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

ricardozanini pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-serverless-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 132d1236 Break the current operator's configuration into custom and 
managed properties (#367)
132d1236 is described below

commit 132d1236cc37e3f80ff10016f5303eb3af59f4d7
Author: Daniele Martinoli <[email protected]>
AuthorDate: Fri Feb 2 21:22:39 2024 +0100

    Break the current operator's configuration into custom and managed 
properties (#367)
    
    * Initial commit
    
    * integrating comments: introducing ObjectEnsurerWithPlatform and 
ObjectCreatorWithPlatform
    
    * Removing fewe more unneeded references to platform
    
    * reviewed workflowProjectHandler. removed user props from managed props
    
    * fixed unit tests
    
    * workarond for failed discovery options
    
    * fixed broken unit tests
    
    * fixed mutator for user props
    
    * reviewed hashing function
    
    * Anticipating deactivation of broken e2e test
    
    * integrating comments: removing unneeded comment
    
    * adding discovered value to properties whose value mathes the service 
discovery pattern
    
    * removed unused package common_test
    
    * Renamed managed props visitor and reviewed description of 
NewAppPropertyHandler
---
 controllers/profiles/common/ensurer.go             | 39 +++++++++++++++
 controllers/profiles/common/mutate_visitors.go     | 32 ++++++------
 controllers/profiles/common/object_creators.go     | 17 +++++--
 .../profiles/common/object_creators_test.go        | 57 ++++++++++++----------
 .../profiles/common/properties/application.go      | 33 ++++++-------
 .../profiles/common/properties/application_test.go | 45 ++++++++++-------
 .../profiles/common/properties/discovery.go        |  2 +
 .../profiles/common/properties/discovery_test.go   |  7 ++-
 controllers/profiles/dev/object_creators_dev.go    |  5 +-
 .../profiles/dev/object_creators_dev_test.go       |  1 -
 controllers/profiles/dev/profile_dev.go            | 33 +++++++------
 controllers/profiles/dev/profile_dev_test.go       | 24 ++++++---
 controllers/profiles/dev/states_dev.go             | 10 ++--
 controllers/profiles/prod/deployment_handler.go    | 26 ++++++----
 .../profiles/prod/deployment_handler_test.go       | 52 ++++++++++++--------
 controllers/profiles/prod/object_creators_prod.go  | 11 +++--
 controllers/profiles/prod/profile_prod.go          | 14 +++---
 utils/kubernetes/deployment.go                     | 18 ++++---
 workflowproj/operator.go                           | 50 +++++++++++++++----
 workflowproj/workflowproj.go                       |  3 +-
 workflowproj/workflowproj_test.go                  |  1 +
 21 files changed, 312 insertions(+), 168 deletions(-)

diff --git a/controllers/profiles/common/ensurer.go 
b/controllers/profiles/common/ensurer.go
index 0d9cb472..7d258dbb 100644
--- a/controllers/profiles/common/ensurer.go
+++ b/controllers/profiles/common/ensurer.go
@@ -36,6 +36,9 @@ var _ ObjectEnsurer = &noopObjectEnsurer{}
 type ObjectEnsurer interface {
        Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, visitors 
...MutateVisitor) (client.Object, controllerutil.OperationResult, error)
 }
+type ObjectEnsurerWithPlatform interface {
+       Ensure(ctx context.Context, workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform, visitors ...MutateVisitor) (client.Object, 
controllerutil.OperationResult, error)
+}
 
 // MutateVisitor is a visitor function that mutates the given object before 
performing any updates in the cluster.
 // It gets called after the objectEnforcer reference.
@@ -56,6 +59,14 @@ func NewObjectEnsurer(client client.Client, creator 
ObjectCreator) ObjectEnsurer
        }
 }
 
+// NewObjectEnsurerWithPlatform see defaultObjectEnsurerWithPLatform
+func NewObjectEnsurerWithPlatform(client client.Client, creator 
ObjectCreatorWithPlatform) ObjectEnsurerWithPlatform {
+       return &defaultObjectEnsurerWithPlatform{
+               c:       client,
+               creator: creator,
+       }
+}
+
 // defaultObjectEnsurer provides the engine for a ReconciliationState that 
needs to create or update a given Kubernetes object during the reconciliation 
cycle.
 type defaultObjectEnsurer struct {
        c       client.Client
@@ -84,6 +95,34 @@ func (d *defaultObjectEnsurer) Ensure(ctx context.Context, 
workflow *operatorapi
        return object, result, nil
 }
 
+// defaultObjectEnsurerWithPlatform is the equivalent of defaultObjectEnsurer 
for resources that require a reference to the SonataFlowPlatform
+type defaultObjectEnsurerWithPlatform struct {
+       c       client.Client
+       creator ObjectCreatorWithPlatform
+}
+
+func (d *defaultObjectEnsurerWithPlatform) Ensure(ctx context.Context, 
workflow *operatorapi.SonataFlow, pl *operatorapi.SonataFlowPlatform, visitors 
...MutateVisitor) (client.Object, controllerutil.OperationResult, error) {
+       result := controllerutil.OperationResultNone
+
+       object, err := d.creator(workflow, pl)
+       if err != nil {
+               return nil, result, err
+       }
+       if result, err = controllerutil.CreateOrPatch(ctx, d.c, object,
+               func() error {
+                       for _, v := range visitors {
+                               if visitorErr := v(object)(); visitorErr != nil 
{
+                                       return visitorErr
+                               }
+                       }
+                       return controllerutil.SetControllerReference(workflow, 
object, d.c.Scheme())
+               }); err != nil {
+               return nil, result, err
+       }
+       klog.V(log.I).InfoS("Object operation finalized", "result", result, 
"kind", object.GetObjectKind().GroupVersionKind().String(), "name", 
object.GetName(), "namespace", object.GetNamespace())
+       return object, result, nil
+}
+
 // NewNoopObjectEnsurer see noopObjectEnsurer
 func NewNoopObjectEnsurer() ObjectEnsurer {
        return &noopObjectEnsurer{}
diff --git a/controllers/profiles/common/mutate_visitors.go 
b/controllers/profiles/common/mutate_visitors.go
index ffe9784a..8ccbcab3 100644
--- a/controllers/profiles/common/mutate_visitors.go
+++ b/controllers/profiles/common/mutate_visitors.go
@@ -105,32 +105,28 @@ func ServiceMutateVisitor(workflow 
*operatorapi.SonataFlow) MutateVisitor {
        }
 }
 
-func WorkflowPropertiesMutateVisitor(ctx context.Context, catalog 
discovery.ServiceCatalog,
-       workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform) MutateVisitor {
+func ManagedPropertiesMutateVisitor(ctx context.Context, catalog 
discovery.ServiceCatalog,
+       workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform, userProps *corev1.ConfigMap) MutateVisitor {
        return func(object client.Object) controllerutil.MutateFn {
                return func() error {
-                       if kubeutil.IsObjectNew(object) {
-                               return nil
-                       }
-                       cm := object.(*corev1.ConfigMap)
-                       cm.Labels = workflow.GetLabels()
-                       _, hasKey := 
cm.Data[workflowproj.ApplicationPropertiesFileName]
+                       managedProps := object.(*corev1.ConfigMap)
+                       managedProps.Labels = workflow.GetLabels()
+                       _, hasKey := 
managedProps.Data[workflowproj.GetManagedPropertiesFileName(workflow)]
                        if !hasKey {
-                               cm.Data = make(map[string]string, 1)
-                               props, err := 
properties.ImmutableApplicationProperties(workflow, platform)
-                               if err != nil {
-                                       return err
-                               }
-                               
cm.Data[workflowproj.ApplicationPropertiesFileName] = props
-                               return nil
+                               managedProps.Data = make(map[string]string, 1)
+                               
managedProps.Data[workflowproj.GetManagedPropertiesFileName(workflow)] = ""
                        }
 
+                       userProperties, hasKey := 
userProps.Data[workflowproj.ApplicationPropertiesFileName]
+                       if !hasKey {
+                               userProperties = ""
+                       }
                        // In the future, if this needs change, instead we can 
receive an AppPropertyHandler in this mutator
                        props, err := 
properties.NewAppPropertyHandler(workflow, platform)
                        if err != nil {
                                return err
                        }
-                       cm.Data[workflowproj.ApplicationPropertiesFileName] = 
props.WithUserProperties(cm.Data[workflowproj.ApplicationPropertiesFileName]).
+                       
managedProps.Data[workflowproj.GetManagedPropertiesFileName(workflow)] = 
props.WithUserProperties(userProperties).
                                WithServiceDiscovery(ctx, catalog).
                                Build()
                        return nil
@@ -142,11 +138,11 @@ func WorkflowPropertiesMutateVisitor(ctx context.Context, 
catalog discovery.Serv
 // This method can be used as an alternative to the Kubernetes ConfigMap 
refresher.
 //
 // See: 
https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically
-func RolloutDeploymentIfCMChangedMutateVisitor(cm *v1.ConfigMap) MutateVisitor 
{
+func RolloutDeploymentIfCMChangedMutateVisitor(workflow 
*operatorapi.SonataFlow, userPropsCM *v1.ConfigMap, managedPropsCM 
*v1.ConfigMap) MutateVisitor {
        return func(object client.Object) controllerutil.MutateFn {
                return func() error {
                        deployment := object.(*appsv1.Deployment)
-                       err := 
kubeutil.AnnotateDeploymentConfigChecksum(deployment, cm)
+                       err := 
kubeutil.AnnotateDeploymentConfigChecksum(workflow, deployment, userPropsCM, 
managedPropsCM)
                        return err
                }
        }
diff --git a/controllers/profiles/common/object_creators.go 
b/controllers/profiles/common/object_creators.go
index 5739b6ad..0e2d6f20 100644
--- a/controllers/profiles/common/object_creators.go
+++ b/controllers/profiles/common/object_creators.go
@@ -41,6 +41,10 @@ import (
 // Can be used as a reference to keep the object immutable
 type ObjectCreator func(workflow *operatorapi.SonataFlow) (client.Object, 
error)
 
+// ObjectCreatorWithPlatform is the func equivalent to ObjectCreator to use 
when the resource being created needs a reference to the
+// SonataFlowPlatform
+type ObjectCreatorWithPlatform func(workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform) (client.Object, error)
+
 const (
        defaultHTTPServicePort = 80
 
@@ -209,13 +213,18 @@ func OpenShiftRouteCreator(workflow 
*operatorapi.SonataFlow) (client.Object, err
        return route, err
 }
 
-// WorkflowPropsConfigMapCreator creates a ConfigMap to hold the external 
application properties
-func WorkflowPropsConfigMapCreator(workflow *operatorapi.SonataFlow) 
(client.Object, error) {
-       props, err := properties.ImmutableApplicationProperties(workflow, nil)
+// UserPropsConfigMapCreator creates an empty ConfigMap to hold the user 
application properties
+func UserPropsConfigMapCreator(workflow *operatorapi.SonataFlow) 
(client.Object, error) {
+       return workflowproj.CreateNewUserPropsConfigMap(workflow), nil
+}
+
+// ManagedPropsConfigMapCreator creates an empty ConfigMap to hold the 
external application properties
+func ManagedPropsConfigMapCreator(workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform) (client.Object, error) {
+       props, err := properties.ImmutableApplicationProperties(workflow, 
platform)
        if err != nil {
                return nil, err
        }
-       return workflowproj.CreateNewAppPropsConfigMap(workflow, props), nil
+       return workflowproj.CreateNewManagedPropsConfigMap(workflow, props), nil
 }
 
 func ConfigurePersistence(serviceContainer *corev1.Container, options 
*operatorapi.PersistenceOptions, defaultSchema, namespace string) 
*corev1.Container {
diff --git a/controllers/profiles/common/object_creators_test.go 
b/controllers/profiles/common/object_creators_test.go
index 64e8f43b..ae252248 100644
--- a/controllers/profiles/common/object_creators_test.go
+++ b/controllers/profiles/common/object_creators_test.go
@@ -27,7 +27,6 @@ import (
        "github.com/stretchr/testify/assert"
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
-       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
        "github.com/apache/incubator-kie-kogito-serverless-operator/utils"
        kubeutil 
"github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes"
@@ -39,50 +38,56 @@ import (
 
 func Test_ensureWorkflowPropertiesConfigMapMutator(t *testing.T) {
        workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
+       platform := test.GetBasePlatform()
        // can't be new
-       cm, _ := WorkflowPropsConfigMapCreator(workflow)
-       cm.SetUID("1")
-       cm.SetResourceVersion("1")
-       reflectCm := cm.(*corev1.ConfigMap)
+       managedProps, _ := ManagedPropsConfigMapCreator(workflow, platform)
+       managedProps.SetUID("1")
+       managedProps.SetResourceVersion("1")
+       managedPropsCM := managedProps.(*corev1.ConfigMap)
 
-       visitor := WorkflowPropertiesMutateVisitor(context.TODO(), nil, 
workflow, nil)
-       mutateFn := visitor(cm)
+       userProps, _ := UserPropsConfigMapCreator(workflow)
+       userPropsCM := userProps.(*corev1.ConfigMap)
+       visitor := ManagedPropertiesMutateVisitor(context.TODO(), nil, 
workflow, nil, userPropsCM)
+       mutateFn := visitor(managedProps)
 
        assert.NoError(t, mutateFn())
-       assert.NotEmpty(t, 
reflectCm.Data[workflowproj.ApplicationPropertiesFileName])
+       assert.Empty(t, 
managedPropsCM.Data[workflowproj.ApplicationPropertiesFileName])
+       assert.NotEmpty(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)])
 
-       props := 
properties.MustLoadString(reflectCm.Data[workflowproj.ApplicationPropertiesFileName])
+       props := 
properties.MustLoadString(managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)])
        assert.Equal(t, "8080", props.GetString("quarkus.http.port", ""))
 
        // we change the properties to something different, we add ours and 
change the default
-       reflectCm.Data[workflowproj.ApplicationPropertiesFileName] = 
"quarkus.http.port=9090\nmy.new.prop=1"
-       visitor(reflectCm)
+       userPropsCM.Data[workflowproj.ApplicationPropertiesFileName] = 
"quarkus.http.port=9090\nmy.new.prop=1"
+       visitor(managedPropsCM)
        assert.NoError(t, mutateFn())
 
        // we should preserve the default, and still got ours
-       props = 
properties.MustLoadString(reflectCm.Data[workflowproj.ApplicationPropertiesFileName])
+       props = 
properties.MustLoadString(managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)])
        assert.Equal(t, "8080", props.GetString("quarkus.http.port", ""))
        assert.Equal(t, "0.0.0.0", props.GetString("quarkus.http.host", ""))
-       assert.Equal(t, "1", props.GetString("my.new.prop", ""))
+       assert.NotContains(t, "my.new.prop", props.Keys())
 }
 
 func Test_ensureWorkflowPropertiesConfigMapMutator_DollarReplacement(t 
*testing.T) {
        workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
-       existingCM := &corev1.ConfigMap{
-               ObjectMeta: metav1.ObjectMeta{
-                       Name:      workflow.Name,
-                       Namespace: workflow.Namespace,
-                       UID:       "0000-0001-0002-0003",
-               },
-               Data: map[string]string{
-                       workflowproj.ApplicationPropertiesFileName: 
"mp.messaging.outgoing.kogito_outgoing_stream.url=${kubernetes:services.v1/event-listener}",
-               },
-       }
-       mutateVisitorFn := WorkflowPropertiesMutateVisitor(context.TODO(), nil, 
workflow, nil)
+       platform := test.GetBasePlatform()
+       managedProps, _ := ManagedPropsConfigMapCreator(workflow, platform)
+       managedProps.SetName(workflow.Name)
+       managedProps.SetNamespace(workflow.Namespace)
+       managedProps.SetUID("0000-0001-0002-0003")
+       managedPropsCM := managedProps.(*corev1.ConfigMap)
+
+       userProps, _ := UserPropsConfigMapCreator(workflow)
+       userPropsCM := userProps.(*corev1.ConfigMap)
+       userPropsCM.Data[workflowproj.ApplicationPropertiesFileName] = 
"mp.messaging.outgoing.kogito_outgoing_stream.url=${kubernetes:services.v1/event-listener}"
+
+       mutateVisitorFn := ManagedPropertiesMutateVisitor(context.TODO(), nil, 
workflow, nil, userPropsCM)
 
-       err := mutateVisitorFn(existingCM)()
+       err := mutateVisitorFn(managedPropsCM)()
        assert.NoError(t, err)
-       assert.Contains(t, 
existingCM.Data[workflowproj.ApplicationPropertiesFileName], 
"${kubernetes:services.v1/event-listener}")
+       assert.NotContains(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)], 
"mp.messaging.outgoing.kogito_outgoing_stream.url")
+       // assert.Contains(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)], 
"${kubernetes:services.v1/event-listener}")
 }
 
 func TestMergePodSpec(t *testing.T) {
diff --git a/controllers/profiles/common/properties/application.go 
b/controllers/profiles/common/properties/application.go
index fda4ec1e..41489140 100644
--- a/controllers/profiles/common/properties/application.go
+++ b/controllers/profiles/common/properties/application.go
@@ -75,36 +75,34 @@ func (a *appPropertyHandler) WithServiceDiscovery(ctx 
context.Context, catalog d
 }
 
 func (a *appPropertyHandler) Build() string {
-       var props *properties.Properties
+       var userProps *properties.Properties
        var propErr error = nil
        if len(a.userProperties) == 0 {
-               props = properties.NewProperties()
+               userProps = properties.NewProperties()
        } else {
-               props, propErr = properties.LoadString(a.userProperties)
+               userProps, propErr = properties.LoadString(a.userProperties)
        }
        if propErr != nil {
                klog.V(log.D).InfoS("Can't load user's property", "workflow", 
a.workflow.Name, "namespace", a.workflow.Namespace, "properties", 
a.userProperties)
-               props = properties.NewProperties()
+               userProps = properties.NewProperties()
        }
        // Disable expansions since it's not our responsibility
        // Property expansion means resolving ${} within the properties and 
environment context. Quarkus will do that in runtime.
-       props.DisableExpansion = true
+       userProps.DisableExpansion = true
 
-       removeDiscoveryProperties(props)
+       removeDiscoveryProperties(userProps)
+       discoveryProps := properties.NewProperties()
        if a.requireServiceDiscovery() {
                // produce the MicroProfileConfigServiceCatalog properties for 
the service discovery property values if any.
-               discoveryProperties := generateDiscoveryProperties(a.ctx, 
a.catalog, props, a.workflow)
-               if discoveryProperties.Len() > 0 {
-                       props.Merge(discoveryProperties)
-               }
+               discoveryProps.Merge(generateDiscoveryProperties(a.ctx, 
a.catalog, userProps, a.workflow))
        }
-       props = utils.NewApplicationPropertiesBuilder().
-               WithInitialProperties(props).
+       userProps = utils.NewApplicationPropertiesBuilder().
+               WithInitialProperties(discoveryProps).
                
WithImmutableProperties(properties.MustLoadString(immutableApplicationProperties)).
                WithDefaultMutableProperties(a.defaultMutableProperties).
                Build()
 
-       return props.String()
+       return userProps.String()
 }
 
 // withKogitoServiceUrl adds the property kogitoServiceUrlProperty to the 
application properties.
@@ -135,13 +133,14 @@ func (a *appPropertyHandler) 
addDefaultMutableProperty(name string, value string
 }
 
 // NewAppPropertyHandler creates a property handler for a given workflow to 
execute in the provided platform.
-// This handler is intended to build the application properties required by 
the workflow to execute properly, note that
-// the produced properties might vary depending on the platfom, for example, 
if the job service managed by the platform
+// This handler is intended to build the managed application properties 
required by the workflow to execute properly together with
+// the user properties defined in the user-managed ConfigMap.
+// Note that the produced properties might vary depending on the platfom, for 
example, if the job service managed by the platform
 // a particular set of properties will be added, etc.
 // By default, the following properties are incorporated:
 // The set of immutable properties provided by the operator. (user can never 
change)
-// The set of defaultMutableProperties that are provided by the operator, and 
that the user might overwrite if it changes
-// the workflow ConfigMap. This set includes for example the required 
properties to connect with the data index and the
+// The set of defaultMutableProperties that are provided by the operator, and 
that the user cannot overwrite even if it changes
+// the user-managed ConfigMap. This set includes for example the required 
properties to connect with the data index and the
 // job service when any of these services are managed by the platform.
 func NewAppPropertyHandler(workflow *operatorapi.SonataFlow, platform 
*operatorapi.SonataFlowPlatform) (AppPropertyHandler, error) {
        handler := &appPropertyHandler{
diff --git a/controllers/profiles/common/properties/application_test.go 
b/controllers/profiles/common/properties/application_test.go
index 543247dd..d0f4d5df 100644
--- a/controllers/profiles/common/properties/application_test.go
+++ b/controllers/profiles/common/properties/application_test.go
@@ -124,9 +124,9 @@ func 
Test_appPropertyHandler_WithUserPropertiesWithNoUserOverrides(t *testing.T)
        assert.NoError(t, err)
        generatedProps, propsErr := 
properties.LoadString(props.WithUserProperties(userProperties).Build())
        assert.NoError(t, propsErr)
-       assert.Equal(t, 9, len(generatedProps.Keys()))
-       assert.Equal(t, "value1", generatedProps.GetString("property1", ""))
-       assert.Equal(t, "value2", generatedProps.GetString("property2", ""))
+       assert.Equal(t, 7, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
        assert.Equal(t, "http://greeting.default";, 
generatedProps.GetString("kogito.service.url", ""))
        assert.Equal(t, "8080", generatedProps.GetString("quarkus.http.port", 
""))
        assert.Equal(t, "0.0.0.0", 
generatedProps.GetString("quarkus.http.host", ""))
@@ -157,13 +157,16 @@ func 
Test_appPropertyHandler_WithUserPropertiesWithServiceDiscovery(t *testing.T
                Build())
        generatedProps.DisableExpansion = true
        assert.NoError(t, propsErr)
-       assert.Equal(t, 23, len(generatedProps.Keys()))
-       assertHasProperty(t, generatedProps, "property1", "value1")
-       assertHasProperty(t, generatedProps, "property2", "value2")
-
-       assertHasProperty(t, generatedProps, "service1", 
"${kubernetes:services.v1/namespace1/my-service1}")
-       assertHasProperty(t, generatedProps, "service2", 
"${kubernetes:services.v1/my-service2}")
-       assertHasProperty(t, generatedProps, "service3", 
"${knative:namespace1/my-kn-service1}")
+       assert.Equal(t, 21, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
+       assertHasProperty(t, generatedProps, "service1", myService1Address)
+       assertHasProperty(t, generatedProps, "service2", myService2Address)
+       assertHasProperty(t, generatedProps, "service3", myKnService1Address)
+       assertHasProperty(t, generatedProps, "service4", myKnService2Address)
+       assertHasProperty(t, generatedProps, "service5", myKnService3Address)
+       assertHasProperty(t, generatedProps, "broker1", myKnBroker1Address)
+       assertHasProperty(t, generatedProps, "broker2", myKnBroker2Address)
 
        
//org.kie.kogito.addons.discovery.kubernetes\:services.v1\/usecase1ยบ/my-service1
 below we use the unescaped vale because the properties.LoadString removes them.
        assertHasProperty(t, generatedProps, 
"org.kie.kogito.addons.discovery.kubernetes:services.v1/namespace1/my-service1",
 myService1Address)
@@ -214,12 +217,12 @@ func 
Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) {
        assert.NoError(t, err)
        generatedProps, propsErr := 
properties.LoadString(props.WithUserProperties(userProperties).Build())
        assert.NoError(t, propsErr)
-       assert.Equal(t, 13, len(generatedProps.Keys()))
-       assert.Equal(t, "value1", generatedProps.GetString("property1", ""))
-       assert.Equal(t, "value2", generatedProps.GetString("property2", ""))
+       assert.Equal(t, 11, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
 
-       //kogito.service.url takes the user provided value since it's a default 
mutable property.
-       assert.Equal(t, "http://myUrl.override.com";, 
generatedProps.GetString("kogito.service.url", ""))
+       //kogito.service.url is a default immutable property.
+       assert.Equal(t, "http://greeting.default";, 
generatedProps.GetString("kogito.service.url", ""))
        //quarkus.http.port remains with the default value since it's immutable.
        assert.Equal(t, "8080", generatedProps.GetString("quarkus.http.port", 
""))
        assert.Equal(t, "0.0.0.0", 
generatedProps.GetString("quarkus.http.host", ""))
@@ -239,7 +242,9 @@ func 
Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) {
        assert.NoError(t, err)
        generatedProps, propsErr = 
properties.LoadString(props.WithUserProperties(userProperties).Build())
        assert.NoError(t, propsErr)
-       assert.Equal(t, 18, len(generatedProps.Keys()))
+       assert.Equal(t, 16, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
        assert.Equal(t, 
"http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace+"/definitions";,
 generatedProps.GetString(constants.KogitoProcessDefinitionsEventsURL, ""))
        assert.Equal(t, "true", 
generatedProps.GetString(constants.KogitoProcessDefinitionsEventsEnabled, ""))
        assert.Equal(t, 
"http://"+platform.Name+"-"+constants.DataIndexServiceName+"."+platform.Namespace+"/processes";,
 generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, ""))
@@ -259,7 +264,9 @@ func 
Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) {
        assert.NoError(t, err)
        generatedProps, propsErr = 
properties.LoadString(props.WithUserProperties(userProperties).Build())
        assert.NoError(t, propsErr)
-       assert.Equal(t, 14, len(generatedProps.Keys()))
+       assert.Equal(t, 12, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
        assert.Equal(t, "", 
generatedProps.GetString(constants.KogitoProcessDefinitionsEventsURL, ""))
        assert.Equal(t, "false", 
generatedProps.GetString(constants.KogitoProcessDefinitionsEventsEnabled, ""))
        assert.Equal(t, "", 
generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, ""))
@@ -276,7 +283,9 @@ func 
Test_appPropertyHandler_WithServicesWithUserOverrides(t *testing.T) {
        assert.NoError(t, err)
        generatedProps, propsErr = 
properties.LoadString(props.WithUserProperties(userProperties).Build())
        assert.NoError(t, propsErr)
-       assert.Equal(t, 13, len(generatedProps.Keys()))
+       assert.Equal(t, 11, len(generatedProps.Keys()))
+       assert.NotContains(t, "property1", generatedProps.Keys())
+       assert.NotContains(t, "property2", generatedProps.Keys())
        assert.Equal(t, "", 
generatedProps.GetString(constants.KogitoProcessDefinitionsEventsURL, ""))
        assert.Equal(t, "false", 
generatedProps.GetString(constants.KogitoProcessDefinitionsEventsEnabled, ""))
        assert.Equal(t, "", 
generatedProps.GetString(constants.KogitoProcessInstancesEventsURL, ""))
diff --git a/controllers/profiles/common/properties/discovery.go 
b/controllers/profiles/common/properties/discovery.go
index a7663d41..6d2ac71b 100644
--- a/controllers/profiles/common/properties/discovery.go
+++ b/controllers/profiles/common/properties/discovery.go
@@ -99,6 +99,8 @@ func generateDiscoveryProperties(ctx context.Context, catalog 
discovery.ServiceC
                                        mpProperty := 
generateMicroprofileServiceCatalogProperty(plainUri)
                                        klog.V(log.I).Infof("Generating 
microprofile service catalog property %s=%s.", mpProperty, address)
                                        result.MustSet(mpProperty, address)
+                                       klog.V(log.I).Infof("Overriding the 
discoverable value as the managed property %s=%s.", k, address)
+                                       result.MustSet(k, address)
                                }
                        }
                }
diff --git a/controllers/profiles/common/properties/discovery_test.go 
b/controllers/profiles/common/properties/discovery_test.go
index 4555c6c3..dd502d18 100644
--- a/controllers/profiles/common/properties/discovery_test.go
+++ b/controllers/profiles/common/properties/discovery_test.go
@@ -62,7 +62,12 @@ func Test_generateDiscoveryProperties(t *testing.T) {
                Spec:       v1alpha08.SonataFlowSpec{Flow: workflow},
        })
 
-       assert.Equal(t, result.Len(), 5)
+       assert.Equal(t, 8, result.Len())
+       assertHasProperty(t, result, "service1", myService1Address)
+       assertHasProperty(t, result, "service2", myService2Address)
+       assertHasProperty(t, result, "service3", myService3Address)
+       assertHasProperty(t, result, 
"org.kie.kogito.addons.discovery.kubernetes\\:services.v1\\/namespace1\\/my-service1",
 myService1Address)
+       assertHasProperty(t, result, 
"org.kie.kogito.addons.discovery.kubernetes\\:services.v1\\/namespace1\\/my-service1",
 myService1Address)
        assertHasProperty(t, result, 
"org.kie.kogito.addons.discovery.kubernetes\\:services.v1\\/namespace1\\/my-service1",
 myService1Address)
        assertHasProperty(t, result, 
"org.kie.kogito.addons.discovery.kubernetes\\:services.v1\\/my-service2", 
myService2Address)
        assertHasProperty(t, result, 
"org.kie.kogito.addons.discovery.kubernetes\\:services.v1\\/my-service3?port\\=http-port",
 myService3Address)
diff --git a/controllers/profiles/dev/object_creators_dev.go 
b/controllers/profiles/dev/object_creators_dev.go
index 1cbb42e4..03b85750 100644
--- a/controllers/profiles/dev/object_creators_dev.go
+++ b/controllers/profiles/dev/object_creators_dev.go
@@ -118,7 +118,7 @@ func ensureWorkflowDefConfigMapMutator(workflow 
*operatorapi.SonataFlow) common.
 }
 
 // mountDevConfigMapsMutateVisitor mounts the required configMaps in the 
Workflow Dev Deployment
-func mountDevConfigMapsMutateVisitor(flowDefCM, propsCM *corev1.ConfigMap, 
workflowResCMs []operatorapi.ConfigMapWorkflowResource) common.MutateVisitor {
+func mountDevConfigMapsMutateVisitor(workflow *operatorapi.SonataFlow, 
flowDefCM, userPropsCM, managedPropsCM *corev1.ConfigMap, workflowResCMs 
[]operatorapi.ConfigMapWorkflowResource) common.MutateVisitor {
        return func(object client.Object) controllerutil.MutateFn {
                return func() error {
                        deployment := object.(*appsv1.Deployment)
@@ -129,7 +129,8 @@ func mountDevConfigMapsMutateVisitor(flowDefCM, propsCM 
*corev1.ConfigMap, workf
 
                        // defaultResourcesVolume holds every ConfigMap mount 
required on src/main/resources
                        defaultResourcesVolume := corev1.Volume{Name: 
configMapResourcesVolumeName, VolumeSource: corev1.VolumeSource{Projected: 
&corev1.ProjectedVolumeSource{}}}
-                       
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
propsCM.Name, corev1.KeyToPath{Key: workflowproj.ApplicationPropertiesFileName, 
Path: workflowproj.ApplicationPropertiesFileName})
+                       
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
userPropsCM.Name, corev1.KeyToPath{Key: 
workflowproj.ApplicationPropertiesFileName, Path: 
workflowproj.ApplicationPropertiesFileName})
+                       
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
managedPropsCM.Name, corev1.KeyToPath{Key: 
workflowproj.GetManagedPropertiesFileName(workflow), Path: 
workflowproj.GetManagedPropertiesFileName(workflow)})
                        
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
flowDefCM.Name)
 
                        // resourceVolumes holds every resource that needs to 
be mounted on src/main/resources/<specific_dir>
diff --git a/controllers/profiles/dev/object_creators_dev_test.go 
b/controllers/profiles/dev/object_creators_dev_test.go
index 37adaf8a..8209940a 100644
--- a/controllers/profiles/dev/object_creators_dev_test.go
+++ b/controllers/profiles/dev/object_creators_dev_test.go
@@ -30,7 +30,6 @@ import (
 
 func Test_ensureWorkflowDevServiceIsExposed(t *testing.T) {
        workflow := test.GetBaseSonataFlowWithDevProfile(t.Name())
-
        //On Kubernetes we want the service exposed in Dev with NodePort
        service, _ := serviceCreator(workflow)
        service.SetUID("1")
diff --git a/controllers/profiles/dev/profile_dev.go 
b/controllers/profiles/dev/profile_dev.go
index dd0ae728..42b9151a 100644
--- a/controllers/profiles/dev/profile_dev.go
+++ b/controllers/profiles/dev/profile_dev.go
@@ -75,21 +75,23 @@ func NewProfileReconciler(client client.Client, cfg 
*rest.Config, recorder recor
 
 func newObjectEnsurers(support *common.StateSupport) *objectEnsurers {
        return &objectEnsurers{
-               deployment:          common.NewObjectEnsurer(support.C, 
deploymentCreator),
-               service:             common.NewObjectEnsurer(support.C, 
serviceCreator),
-               network:             common.NewNoopObjectEnsurer(),
-               definitionConfigMap: common.NewObjectEnsurer(support.C, 
workflowDefConfigMapCreator),
-               propertiesConfigMap: common.NewObjectEnsurer(support.C, 
common.WorkflowPropsConfigMapCreator),
+               deployment:            common.NewObjectEnsurer(support.C, 
deploymentCreator),
+               service:               common.NewObjectEnsurer(support.C, 
serviceCreator),
+               network:               common.NewNoopObjectEnsurer(),
+               definitionConfigMap:   common.NewObjectEnsurer(support.C, 
workflowDefConfigMapCreator),
+               userPropsConfigMap:    common.NewObjectEnsurer(support.C, 
common.UserPropsConfigMapCreator),
+               managedPropsConfigMap: 
common.NewObjectEnsurerWithPlatform(support.C, 
common.ManagedPropsConfigMapCreator),
        }
 }
 
 func newObjectEnsurersOpenShift(support *common.StateSupport) *objectEnsurers {
        return &objectEnsurers{
-               deployment:          common.NewObjectEnsurer(support.C, 
deploymentCreator),
-               service:             common.NewObjectEnsurer(support.C, 
serviceCreator),
-               network:             common.NewObjectEnsurer(support.C, 
common.OpenShiftRouteCreator),
-               definitionConfigMap: common.NewObjectEnsurer(support.C, 
workflowDefConfigMapCreator),
-               propertiesConfigMap: common.NewObjectEnsurer(support.C, 
common.WorkflowPropsConfigMapCreator),
+               deployment:            common.NewObjectEnsurer(support.C, 
deploymentCreator),
+               service:               common.NewObjectEnsurer(support.C, 
serviceCreator),
+               network:               common.NewObjectEnsurer(support.C, 
common.OpenShiftRouteCreator),
+               definitionConfigMap:   common.NewObjectEnsurer(support.C, 
workflowDefConfigMapCreator),
+               userPropsConfigMap:    common.NewObjectEnsurer(support.C, 
common.UserPropsConfigMapCreator),
+               managedPropsConfigMap: 
common.NewObjectEnsurerWithPlatform(support.C, 
common.ManagedPropsConfigMapCreator),
        }
 }
 
@@ -106,11 +108,12 @@ func newStatusEnrichersOpenShift(support 
*common.StateSupport) *statusEnrichers
 }
 
 type objectEnsurers struct {
-       deployment          common.ObjectEnsurer
-       service             common.ObjectEnsurer
-       network             common.ObjectEnsurer
-       definitionConfigMap common.ObjectEnsurer
-       propertiesConfigMap common.ObjectEnsurer
+       deployment            common.ObjectEnsurer
+       service               common.ObjectEnsurer
+       network               common.ObjectEnsurer
+       definitionConfigMap   common.ObjectEnsurer
+       userPropsConfigMap    common.ObjectEnsurer
+       managedPropsConfigMap common.ObjectEnsurerWithPlatform
 }
 
 type statusEnrichers struct {
diff --git a/controllers/profiles/dev/profile_dev_test.go 
b/controllers/profiles/dev/profile_dev_test.go
index 5c2591d0..1c3478ea 100644
--- a/controllers/profiles/dev/profile_dev_test.go
+++ b/controllers/profiles/dev/profile_dev_test.go
@@ -145,12 +145,16 @@ func Test_newDevProfile(t *testing.T) {
        assert.Equal(t, quarkusDevConfigMountPath, 
deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
        assert.Equal(t, "", 
deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath) 
//https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically
 
-       propCM := &corev1.ConfigMap{}
-       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowPropertiesConfigMapName(workflow)}, propCM)
-       assert.NotEmpty(t, 
propCM.Data[workflowproj.ApplicationPropertiesFileName])
+       userPropsCM := &corev1.ConfigMap{}
+       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowUserPropertiesConfigMapName(workflow)}, userPropsCM)
+       assert.Empty(t, 
userPropsCM.Data[workflowproj.ApplicationPropertiesFileName])
+
+       managedPropsCM := &corev1.ConfigMap{}
+       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowManagedPropertiesConfigMapName(workflow)}, 
managedPropsCM)
+       assert.NotEmpty(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)])
        assert.Equal(t, quarkusDevConfigMountPath, 
deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
        assert.Equal(t, "", 
deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath) 
//https://kubernetes.io/docs/concepts/configuration/configmap/#mounted-configmaps-are-updated-automatically
-       assert.Contains(t, 
propCM.Data[workflowproj.ApplicationPropertiesFileName], "quarkus.http.port")
+       assert.Contains(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)], 
"quarkus.http.port")
 
        service := test.MustGetService(t, client, workflow)
        assert.Equal(t, int32(constants.DefaultHTTPWorkflowPortInt), 
service.Spec.Ports[0].TargetPort.IntVal)
@@ -179,10 +183,14 @@ func Test_newDevProfile(t *testing.T) {
        err = client.Update(context.TODO(), deployment)
        assert.NoError(t, err)
 
-       propCM = &corev1.ConfigMap{}
-       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowPropertiesConfigMapName(workflow)}, propCM)
-       assert.NotEmpty(t, 
propCM.Data[workflowproj.ApplicationPropertiesFileName])
-       assert.Contains(t, 
propCM.Data[workflowproj.ApplicationPropertiesFileName], "quarkus.http.port")
+       userPropsCM = &corev1.ConfigMap{}
+       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowUserPropertiesConfigMapName(workflow)}, userPropsCM)
+       assert.Empty(t, 
userPropsCM.Data[workflowproj.ApplicationPropertiesFileName])
+
+       managedPropsCM = &corev1.ConfigMap{}
+       _ = client.Get(context.TODO(), types.NamespacedName{Namespace: 
workflow.Namespace, Name: 
workflowproj.GetWorkflowManagedPropertiesConfigMapName(workflow)}, 
managedPropsCM)
+       assert.NotEmpty(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)])
+       assert.Contains(t, 
managedPropsCM.Data[workflowproj.GetManagedPropertiesFileName(workflow)], 
"quarkus.http.port")
 
        // reconcile
        workflow.Status.Manager().MarkTrue(api.RunningConditionType)
diff --git a/controllers/profiles/dev/states_dev.go 
b/controllers/profiles/dev/states_dev.go
index e965547e..f98e03eb 100644
--- a/controllers/profiles/dev/states_dev.go
+++ b/controllers/profiles/dev/states_dev.go
@@ -74,11 +74,15 @@ func (e *ensureRunningWorkflowState) Do(ctx 
context.Context, workflow *operatora
        if err == nil && len(pl.Spec.DevMode.BaseImage) > 0 {
                devBaseContainerImage = pl.Spec.DevMode.BaseImage
        }
-       propsCM, _, err := e.ensurers.propertiesConfigMap.Ensure(ctx, workflow, 
common.WorkflowPropertiesMutateVisitor(ctx, e.StateSupport.Catalog, workflow, 
pl))
+       userPropsCM, _, err := e.ensurers.userPropsConfigMap.Ensure(ctx, 
workflow)
        if err != nil {
                return ctrl.Result{Requeue: false}, objs, err
        }
-       objs = append(objs, propsCM)
+       managedPropsCM, _, err := e.ensurers.managedPropsConfigMap.Ensure(ctx, 
workflow, pl, common.ManagedPropertiesMutateVisitor(ctx, 
e.StateSupport.Catalog, workflow, pl, userPropsCM.(*corev1.ConfigMap)))
+       if err != nil {
+               return ctrl.Result{Requeue: false}, objs, err
+       }
+       objs = append(objs, managedPropsCM)
 
        externalCM, err := workflowdef.FetchExternalResourcesConfigMapsRef(e.C, 
workflow)
        if err != nil {
@@ -92,7 +96,7 @@ func (e *ensureRunningWorkflowState) Do(ctx context.Context, 
workflow *operatora
        deployment, _, err := e.ensurers.deployment.Ensure(ctx, workflow,
                deploymentMutateVisitor(workflow),
                common.ImageDeploymentMutateVisitor(workflow, 
devBaseContainerImage),
-               mountDevConfigMapsMutateVisitor(flowDefCM.(*corev1.ConfigMap), 
propsCM.(*corev1.ConfigMap), externalCM))
+               mountDevConfigMapsMutateVisitor(workflow, 
flowDefCM.(*corev1.ConfigMap), userPropsCM.(*corev1.ConfigMap), 
managedPropsCM.(*corev1.ConfigMap), externalCM))
        if err != nil {
                return ctrl.Result{RequeueAfter: 
constants.RequeueAfterFailure}, objs, err
        }
diff --git a/controllers/profiles/prod/deployment_handler.go 
b/controllers/profiles/prod/deployment_handler.go
index a5459a87..3b693429 100644
--- a/controllers/profiles/prod/deployment_handler.go
+++ b/controllers/profiles/prod/deployment_handler.go
@@ -49,9 +49,16 @@ func (d *deploymentReconciler) reconcile(ctx 
context.Context, workflow *operator
 
 func (d *deploymentReconciler) reconcileWithBuiltImage(ctx context.Context, 
workflow *operatorapi.SonataFlow, image string) (reconcile.Result, 
[]client.Object, error) {
        pl, _ := platform.GetActivePlatform(ctx, d.C, workflow.Namespace)
-       propsCM, _, err := d.ensurers.propertiesConfigMap.Ensure(ctx, workflow, 
common.WorkflowPropertiesMutateVisitor(ctx, d.StateSupport.Catalog, workflow, 
pl))
+       userPropsCM, _, err := d.ensurers.userPropsConfigMap.Ensure(ctx, 
workflow)
        if err != nil {
-               workflow.Status.Manager().MarkFalse(api.RunningConditionType, 
api.ExternalResourcesNotFoundReason, "Unable to retrieve the properties config 
map")
+               workflow.Status.Manager().MarkFalse(api.RunningConditionType, 
api.ExternalResourcesNotFoundReason, "Unable to retrieve the user properties 
config map")
+               _, err = d.PerformStatusUpdate(ctx, workflow)
+               return ctrl.Result{}, nil, err
+       }
+       managedPropsCM, _, err := d.ensurers.managedPropsConfigMap.Ensure(ctx, 
workflow, pl,
+               common.ManagedPropertiesMutateVisitor(ctx, 
d.StateSupport.Catalog, workflow, pl, userPropsCM.(*v1.ConfigMap)))
+       if err != nil {
+               workflow.Status.Manager().MarkFalse(api.RunningConditionType, 
api.ExternalResourcesNotFoundReason, "Unable to retrieve the managed properties 
config map")
                _, err = d.PerformStatusUpdate(ctx, workflow)
                return ctrl.Result{}, nil, err
        }
@@ -60,7 +67,7 @@ func (d *deploymentReconciler) reconcileWithBuiltImage(ctx 
context.Context, work
                d.ensurers.deployment.Ensure(
                        ctx,
                        workflow,
-                       d.getDeploymentMutateVisitors(workflow, image, 
propsCM.(*v1.ConfigMap))...,
+                       d.getDeploymentMutateVisitors(workflow, image, 
userPropsCM.(*v1.ConfigMap), managedPropsCM.(*v1.ConfigMap))...,
                )
        if err != nil {
                workflow.Status.Manager().MarkFalse(api.RunningConditionType, 
api.DeploymentUnavailableReason, "Unable to perform the deploy due to ", err)
@@ -75,7 +82,7 @@ func (d *deploymentReconciler) reconcileWithBuiltImage(ctx 
context.Context, work
                return reconcile.Result{}, nil, err
        }
 
-       objs := []client.Object{deployment, service, propsCM}
+       objs := []client.Object{deployment, service, managedPropsCM}
 
        if deploymentOp == controllerutil.OperationResultCreated {
                workflow.Status.Manager().MarkFalse(api.RunningConditionType, 
api.WaitingForDeploymentReason, "")
@@ -100,17 +107,18 @@ func (d *deploymentReconciler) 
reconcileWithBuiltImage(ctx context.Context, work
 func (d *deploymentReconciler) getDeploymentMutateVisitors(
        workflow *operatorapi.SonataFlow,
        image string,
-       configMap *v1.ConfigMap) []common.MutateVisitor {
+       userPropsCM *v1.ConfigMap,
+       managedPropsCM *v1.ConfigMap) []common.MutateVisitor {
        if utils.IsOpenShift() {
                return 
[]common.MutateVisitor{common.DeploymentMutateVisitor(workflow),
-                       mountProdConfigMapsMutateVisitor(configMap),
+                       mountProdConfigMapsMutateVisitor(workflow, userPropsCM, 
managedPropsCM),
                        
addOpenShiftImageTriggerDeploymentMutateVisitor(workflow, image),
                        common.ImageDeploymentMutateVisitor(workflow, image),
-                       
common.RolloutDeploymentIfCMChangedMutateVisitor(configMap),
+                       
common.RolloutDeploymentIfCMChangedMutateVisitor(workflow, userPropsCM, 
managedPropsCM),
                }
        }
        return []common.MutateVisitor{common.DeploymentMutateVisitor(workflow),
                common.ImageDeploymentMutateVisitor(workflow, image),
-               mountProdConfigMapsMutateVisitor(configMap),
-               common.RolloutDeploymentIfCMChangedMutateVisitor(configMap)}
+               mountProdConfigMapsMutateVisitor(workflow, userPropsCM, 
managedPropsCM),
+               common.RolloutDeploymentIfCMChangedMutateVisitor(workflow, 
userPropsCM, managedPropsCM)}
 }
diff --git a/controllers/profiles/prod/deployment_handler_test.go 
b/controllers/profiles/prod/deployment_handler_test.go
index 5adfae68..a133b963 100644
--- a/controllers/profiles/prod/deployment_handler_test.go
+++ b/controllers/profiles/prod/deployment_handler_test.go
@@ -26,6 +26,7 @@ import (
        "github.com/stretchr/testify/assert"
        v1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/types"
        utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 )
 
@@ -77,8 +78,12 @@ func Test_CheckDeploymentRolloutAfterCMChange(t *testing.T) {
        assert.NotEmpty(t, objects)
        assert.True(t, result.Requeue)
 
+       userPropsCM := &corev1.ConfigMap{}
+       err = client.Get(context.TODO(), types.NamespacedName{Name: 
workflowproj.GetWorkflowUserPropertiesConfigMapName(workflow), Namespace: 
t.Name()}, userPropsCM)
+       assert.NoError(t, err)
+
        // Second reconciliation, we do change the configmap and that must 
rollout the deployment
-       var cm *corev1.ConfigMap
+       var managedPropsCM *corev1.ConfigMap
        var checksum string
        for _, o := range objects {
                if _, ok := o.(*v1.Deployment); ok {
@@ -89,16 +94,20 @@ func Test_CheckDeploymentRolloutAfterCMChange(t *testing.T) 
{
                        assert.NotContains(t, 
deployment.Spec.Template.ObjectMeta.Annotations, metadata.RestartedAt)
                }
                if _, ok := o.(*corev1.ConfigMap); ok {
-                       cm = o.(*corev1.ConfigMap)
-                       currentProps := 
cm.Data[workflowproj.ApplicationPropertiesFileName]
-                       props, err := properties.LoadString(currentProps)
-                       assert.Nil(t, err)
-                       props.MustSet("test.property", "test.value")
-                       cm.Data[workflowproj.ApplicationPropertiesFileName] = 
props.String()
+                       cm := o.(*corev1.ConfigMap)
+                       if cm.Name == 
workflowproj.GetWorkflowManagedPropertiesConfigMapName(workflow) {
+                               managedPropsCM = cm
+                       }
                }
        }
-       assert.NotNil(t, cm)
-       utilruntime.Must(client.Update(context.TODO(), cm))
+       assert.NotNil(t, managedPropsCM)
+
+       currentProps := 
userPropsCM.Data[workflowproj.ApplicationPropertiesFileName]
+       props, err := properties.LoadString(currentProps)
+       assert.Nil(t, err)
+       props.MustSet("test.property", "test.value")
+       userPropsCM.Data[workflowproj.ApplicationPropertiesFileName] = 
props.String()
+       utilruntime.Must(client.Update(context.TODO(), userPropsCM))
        result, objects, err = handler.reconcile(context.TODO(), workflow)
        assert.NoError(t, err)
        assert.NotEmpty(t, objects)
@@ -131,9 +140,13 @@ func Test_CheckDeploymentUnchangedAfterCMChangeOtherKeys(t 
*testing.T) {
        assert.NotEmpty(t, objects)
        assert.True(t, result.Requeue)
 
+       userPropsCM := &corev1.ConfigMap{}
+       err = client.Get(context.TODO(), types.NamespacedName{Name: 
workflowproj.GetWorkflowUserPropertiesConfigMapName(workflow), Namespace: 
t.Name()}, userPropsCM)
+       assert.NoError(t, err)
+
        // Second reconciliation, we do change the configmap and that must not 
rollout the deployment
        // because we're not updating the application.properties key
-       var cm *corev1.ConfigMap
+       var managedPropsCM *corev1.ConfigMap
        var checksum string
        for _, o := range objects {
                if _, ok := o.(*v1.Deployment); ok {
@@ -144,12 +157,16 @@ func 
Test_CheckDeploymentUnchangedAfterCMChangeOtherKeys(t *testing.T) {
                        assert.NotContains(t, 
deployment.Spec.Template.ObjectMeta.Annotations, metadata.RestartedAt)
                }
                if _, ok := o.(*corev1.ConfigMap); ok {
-                       cm = o.(*corev1.ConfigMap)
-                       cm.Data["other.key"] = "useless.key = value"
+                       cm := o.(*corev1.ConfigMap)
+                       if cm.Name == 
workflowproj.GetWorkflowManagedPropertiesConfigMapName(workflow) {
+                               managedPropsCM = cm
+                       }
                }
        }
-       assert.NotNil(t, cm)
-       utilruntime.Must(client.Update(context.TODO(), cm))
+       assert.NotNil(t, managedPropsCM)
+
+       userPropsCM.Data["other.key"] = "useless.key = value"
+       utilruntime.Must(client.Update(context.TODO(), userPropsCM))
        result, objects, err = handler.reconcile(context.TODO(), workflow)
        assert.NoError(t, err)
        assert.NotEmpty(t, objects)
@@ -157,13 +174,10 @@ func 
Test_CheckDeploymentUnchangedAfterCMChangeOtherKeys(t *testing.T) {
        for _, o := range objects {
                if _, ok := o.(*v1.Deployment); ok {
                        deployment := o.(*v1.Deployment)
-                       // Commented while waiting for SRVLOGIC-195 to be 
addressed
-                       // assert.NotContains(t, 
deployment.Spec.Template.ObjectMeta.Annotations, metadata.RestartedAt)
-                       assert.Contains(t, 
deployment.Spec.Template.ObjectMeta.Annotations, metadata.Checksum)
+                       assert.NotContains(t, 
deployment.Spec.Template.ObjectMeta.Annotations, metadata.RestartedAt)
                        newChecksum := 
deployment.Spec.Template.ObjectMeta.Annotations[metadata.Checksum]
                        assert.NotEmpty(t, newChecksum)
-                       // Change to asssert.Equal when SRVLOGIC-195 is 
addressed
-                       assert.NotEqual(t, newChecksum, checksum)
+                       assert.Equal(t, newChecksum, checksum)
                        break
                }
        }
diff --git a/controllers/profiles/prod/object_creators_prod.go 
b/controllers/profiles/prod/object_creators_prod.go
index 34d1447e..02328e9b 100644
--- a/controllers/profiles/prod/object_creators_prod.go
+++ b/controllers/profiles/prod/object_creators_prod.go
@@ -28,6 +28,7 @@ import (
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 
        
"github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
+       operatorapi 
"github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
 
        
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common"
        
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants"
@@ -64,7 +65,7 @@ func addOpenShiftImageTriggerDeploymentMutateVisitor(workflow 
*v1alpha08.SonataF
 }
 
 // mountDevConfigMapsMutateVisitor mounts the required configMaps in the 
Workflow Dev Deployment
-func mountProdConfigMapsMutateVisitor(propsCM *v1.ConfigMap) 
common.MutateVisitor {
+func mountProdConfigMapsMutateVisitor(workflow *operatorapi.SonataFlow, 
userPropsCM *v1.ConfigMap, managedPropsCM *v1.ConfigMap) common.MutateVisitor {
        return func(object client.Object) controllerutil.MutateFn {
                return func() error {
                        deployment := object.(*appsv1.Deployment)
@@ -77,12 +78,14 @@ func mountProdConfigMapsMutateVisitor(propsCM 
*v1.ConfigMap) common.MutateVisito
                                
deployment.Spec.Template.Spec.Containers[idx].VolumeMounts = 
make([]v1.VolumeMount, 0, 1)
                        }
 
-                       
kubeutil.AddOrReplaceVolume(&deployment.Spec.Template.Spec,
-                               
kubeutil.VolumeConfigMap(constants.ConfigMapWorkflowPropsVolumeName, 
propsCM.Name, v1.KeyToPath{Key: workflowproj.ApplicationPropertiesFileName, 
Path: workflowproj.ApplicationPropertiesFileName}))
+                       defaultResourcesVolume := v1.Volume{Name: 
constants.ConfigMapWorkflowPropsVolumeName, VolumeSource: 
v1.VolumeSource{Projected: &v1.ProjectedVolumeSource{}}}
+                       
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
userPropsCM.Name, v1.KeyToPath{Key: workflowproj.ApplicationPropertiesFileName, 
Path: workflowproj.ApplicationPropertiesFileName})
+                       
kubeutil.VolumeProjectionAddConfigMap(defaultResourcesVolume.Projected, 
managedPropsCM.Name, v1.KeyToPath{Key: 
workflowproj.GetManagedPropertiesFileName(workflow), Path: 
workflowproj.GetManagedPropertiesFileName(workflow)})
+                       
kubeutil.AddOrReplaceVolume(&deployment.Spec.Template.Spec, 
defaultResourcesVolume)
                        kubeutil.AddOrReplaceVolumeMount(idx, 
&deployment.Spec.Template.Spec,
                                
kubeutil.VolumeMount(constants.ConfigMapWorkflowPropsVolumeName, true, 
quarkusProdConfigMountPath))
 
-                       kubeutil.AnnotateDeploymentConfigChecksum(deployment, 
propsCM)
+                       kubeutil.AnnotateDeploymentConfigChecksum(workflow, 
deployment, userPropsCM, managedPropsCM)
                        return nil
                }
        }
diff --git a/controllers/profiles/prod/profile_prod.go 
b/controllers/profiles/prod/profile_prod.go
index 062359ee..f5046d05 100644
--- a/controllers/profiles/prod/profile_prod.go
+++ b/controllers/profiles/prod/profile_prod.go
@@ -52,16 +52,18 @@ const (
 // ReconciliationState that needs access to it must include this struct as an 
attribute and initialize it in the profile builder.
 // Use newObjectEnsurers to facilitate building this struct
 type objectEnsurers struct {
-       deployment          common.ObjectEnsurer
-       service             common.ObjectEnsurer
-       propertiesConfigMap common.ObjectEnsurer
+       deployment            common.ObjectEnsurer
+       service               common.ObjectEnsurer
+       userPropsConfigMap    common.ObjectEnsurer
+       managedPropsConfigMap common.ObjectEnsurerWithPlatform
 }
 
 func newObjectEnsurers(support *common.StateSupport) *objectEnsurers {
        return &objectEnsurers{
-               deployment:          common.NewObjectEnsurer(support.C, 
common.DeploymentCreator),
-               service:             common.NewObjectEnsurer(support.C, 
common.ServiceCreator),
-               propertiesConfigMap: common.NewObjectEnsurer(support.C, 
common.WorkflowPropsConfigMapCreator),
+               deployment:            common.NewObjectEnsurer(support.C, 
common.DeploymentCreator),
+               service:               common.NewObjectEnsurer(support.C, 
common.ServiceCreator),
+               userPropsConfigMap:    common.NewObjectEnsurer(support.C, 
common.UserPropsConfigMapCreator),
+               managedPropsConfigMap: 
common.NewObjectEnsurerWithPlatform(support.C, 
common.ManagedPropsConfigMapCreator),
        }
 }
 
diff --git a/utils/kubernetes/deployment.go b/utils/kubernetes/deployment.go
index ab8dd940..676c4992 100644
--- a/utils/kubernetes/deployment.go
+++ b/utils/kubernetes/deployment.go
@@ -27,6 +27,7 @@ import (
        "time"
 
        
"github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata"
+       operatorapi 
"github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
        "github.com/apache/incubator-kie-kogito-serverless-operator/log"
        
"github.com/apache/incubator-kie-kogito-serverless-operator/workflowproj"
        appsv1 "k8s.io/api/apps/v1"
@@ -112,7 +113,7 @@ func MarkDeploymentToRollout(deployment *appsv1.Deployment) 
error {
 // AnnotateDeploymentConfigChecksum adds the checksum/config annotation to the 
template annotations of the Deployment to set the current configuration.
 // If the checksum has changed from the previous value, the restartedAt 
annotation is also added and a new rollout is started.
 // Code adapted from here: 
https://github.com/kubernetes/kubectl/blob/release-1.26/pkg/polymorphichelpers/objectrestarter.go#L44
-func AnnotateDeploymentConfigChecksum(deployment *appsv1.Deployment, cm 
*v1.ConfigMap) error {
+func AnnotateDeploymentConfigChecksum(workflow *operatorapi.SonataFlow, 
deployment *appsv1.Deployment, userPropsCM *v1.ConfigMap, managedPropsCM 
*v1.ConfigMap) error {
        if deployment.Spec.Paused {
                return errors.New("can't restart paused deployment (run rollout 
resume first)")
        }
@@ -124,7 +125,7 @@ func AnnotateDeploymentConfigChecksum(deployment 
*appsv1.Deployment, cm *v1.Conf
        if !ok {
                currentChecksum = ""
        }
-       newChecksum, err := configMapChecksum(cm)
+       newChecksum, err := calculateHash(userPropsCM, managedPropsCM, workflow)
        if err != nil {
                return err
        }
@@ -141,14 +142,19 @@ func AnnotateDeploymentConfigChecksum(deployment 
*appsv1.Deployment, cm *v1.Conf
        return nil
 }
 
-func configMapChecksum(cm *v1.ConfigMap) (string, error) {
-       props, hasKey := cm.Data[workflowproj.ApplicationPropertiesFileName]
+func dataFromCM(cm *v1.ConfigMap, key string) string {
+       data, hasKey := cm.Data[key]
        if !hasKey {
-               props = ""
+               return ""
        }
+       return data
+}
 
+func calculateHash(userPropsCM, managedPropsCM *v1.ConfigMap, workflow 
*operatorapi.SonataFlow) (string, error) {
+       aggregatedProps := fmt.Sprintf("%s,%s", dataFromCM(userPropsCM, 
workflowproj.ApplicationPropertiesFileName),
+               dataFromCM(managedPropsCM, 
workflowproj.GetManagedPropertiesFileName(workflow)))
        hash := sha256.New()
-       _, err := hash.Write([]byte(props))
+       _, err := hash.Write([]byte(aggregatedProps))
        if err != nil {
                return "", err
        }
diff --git a/workflowproj/operator.go b/workflowproj/operator.go
index 7821c36a..33b4ca67 100644
--- a/workflowproj/operator.go
+++ b/workflowproj/operator.go
@@ -20,18 +20,22 @@
 package workflowproj
 
 import (
+       "fmt"
+
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
 
        
"github.com/apache/incubator-kie-kogito-serverless-operator/api/metadata"
        operatorapi 
"github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
+       
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles"
 )
 
 const (
-       workflowConfigMapNameSuffix = "-props"
-       // ApplicationPropertiesFileName is the default application properties 
file name
-       ApplicationPropertiesFileName = "application.properties"
+       workflowUserConfigMapNameSuffix = "-props"
+       // ApplicationPropertiesFileName is the default application properties 
file name holding user properties
+       ApplicationPropertiesFileName      = "application.properties"
+       workflowManagedConfigMapNameSuffix = "-managed-props"
        // LabelApp key to use among object selectors, "app" is used among k8s 
applications to group objects in some UI consoles
        LabelApp = "app"
        // LabelService key to use among object selectors
@@ -60,9 +64,23 @@ func SetTypeToObject(obj runtime.Object, s *runtime.Scheme) 
error {
        return nil
 }
 
-// GetWorkflowPropertiesConfigMapName gets the default ConfigMap name that 
holds the application property for the given workflow
-func GetWorkflowPropertiesConfigMapName(workflow *operatorapi.SonataFlow) 
string {
-       return workflow.Name + workflowConfigMapNameSuffix
+// GetWorkflowUserPropertiesConfigMapName gets the default ConfigMap name that 
holds the user application property for the given workflow
+func GetWorkflowUserPropertiesConfigMapName(workflow *operatorapi.SonataFlow) 
string {
+       return workflow.Name + workflowUserConfigMapNameSuffix
+}
+
+// GetWorkflowManagedPropertiesConfigMapName gets the default ConfigMap name 
that holds the managed application property for the given workflow
+func GetWorkflowManagedPropertiesConfigMapName(workflow 
*operatorapi.SonataFlow) string {
+       return workflow.Name + workflowManagedConfigMapNameSuffix
+}
+
+// GetWorkflowManagedPropertiesConfigMapName gets the default ConfigMap name 
that holds the managed application property for the given workflow
+func GetManagedPropertiesFileName(workflow *operatorapi.SonataFlow) string {
+       profile := metadata.ProdProfile
+       if profiles.IsDevProfile(workflow) {
+               profile = metadata.DevProfile
+       }
+       return fmt.Sprintf("application-%s.properties", profile)
 }
 
 // SetDefaultLabels adds the default workflow application labels to the given 
object.
@@ -80,15 +98,27 @@ func GetDefaultLabels(workflow *operatorapi.SonataFlow) 
map[string]string {
        }
 }
 
-// CreateNewAppPropsConfigMap creates a new ConfigMap object to hold the 
workflow application properties.
-func CreateNewAppPropsConfigMap(workflow *operatorapi.SonataFlow, properties 
string) *corev1.ConfigMap {
+// CreateNewUserPropsConfigMap creates a new empty ConfigMap object to hold 
the user application properties of the workflow.
+func CreateNewUserPropsConfigMap(workflow *operatorapi.SonataFlow) 
*corev1.ConfigMap {
+       return &corev1.ConfigMap{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      
GetWorkflowUserPropertiesConfigMapName(workflow),
+                       Namespace: workflow.Namespace,
+                       Labels:    GetDefaultLabels(workflow),
+               },
+               Data: map[string]string{ApplicationPropertiesFileName: ""},
+       }
+}
+
+// CreateNewManagedPropsConfigMap creates a new ConfigMap object to hold the 
managed application properties of the workflos.
+func CreateNewManagedPropsConfigMap(workflow *operatorapi.SonataFlow, 
properties string) *corev1.ConfigMap {
        return &corev1.ConfigMap{
                ObjectMeta: metav1.ObjectMeta{
-                       Name:      GetWorkflowPropertiesConfigMapName(workflow),
+                       Name:      
GetWorkflowManagedPropertiesConfigMapName(workflow),
                        Namespace: workflow.Namespace,
                        Labels:    GetDefaultLabels(workflow),
                },
-               Data: map[string]string{ApplicationPropertiesFileName: 
properties},
+               Data: map[string]string{GetManagedPropertiesFileName(workflow): 
properties},
        }
 }
 
diff --git a/workflowproj/workflowproj.go b/workflowproj/workflowproj.go
index 3b46f8c9..afabd7e5 100644
--- a/workflowproj/workflowproj.go
+++ b/workflowproj/workflowproj.go
@@ -247,7 +247,8 @@ func (w *workflowProjectHandler) parseRawAppProperties() 
error {
        if err != nil {
                return err
        }
-       w.project.Properties = CreateNewAppPropsConfigMap(w.project.Workflow, 
string(appPropsContent))
+       w.project.Properties = CreateNewUserPropsConfigMap(w.project.Workflow)
+       w.project.Properties.Data[ApplicationPropertiesFileName] = 
string(appPropsContent)
        if err = SetTypeToObject(w.project.Properties, w.scheme); err != nil {
                return err
        }
diff --git a/workflowproj/workflowproj_test.go 
b/workflowproj/workflowproj_test.go
index 0bc9679c..c1207527 100644
--- a/workflowproj/workflowproj_test.go
+++ b/workflowproj/workflowproj_test.go
@@ -61,6 +61,7 @@ func Test_Handler_WorkflowMinimalAndProps(t *testing.T) {
        assert.NotNil(t, proj.Properties)
        assert.Equal(t, "minimal", proj.Workflow.Name)
        assert.Equal(t, "minimal-props", proj.Properties.Name)
+       assert.NotEmpty(t, proj.Properties.Data["application.properties"])
        assert.Equal(t, string(metadata.ProdProfile), 
proj.Workflow.Annotations[metadata.Profile])
        assert.NotEmpty(t, proj.Properties.Data)
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to