Repository: apache/camel-k (go)

Issue:
Misleading "service.enabled=false" when used in combination with the prometheus trait
Assuming I run an integration with 

    -t service.enabled=false -t prometheus.enabled=true

My expectation is that no services are created, but instead the `prometheus` trait does create it so I'm wondering if we should re-think the `service` trait and reason more in term of container port so:

- every trait could add ports definitions 
- the `service` trait will assemble the service as final step

```go
type containerTrait struct {
	BaseTrait `property:",squash"`

	RequestCPU      string `property:"request-cpu"`
	RequestMemory   string `property:"request-memory"`
	LimitCPU        string `property:"limit-cpu"`
	LimitMemory     string `property:"limit-memory"`

	Port            int32  `property:"port"`
	PortName        string `property:"port-name"`
	ServicePort     int32  `property:"service-port"`
	ServicePortName int32  `property:"service-port-name"`
}

type prometheusTrait struct {
	BaseTrait `property:",squash"`

	Port                 int    `property:"port"`
	PortName             string `property:"port-name"`
	ServicePort          int32  `property:"service-port"`
	ServicePortName      int32  `property:"service-port-name"`

	ServiceMonitor       bool   `property:"service-monitor"`
	ServiceMonitorLabels string `property:"service-monitor-labels"`
}
```


Pull request:
Misleading "service.enabled=false" when used in combination with the prometheus trait i
Fixes #768

Patch:
diff --git a/docs/modules/ROOT/pages/traits.adoc b/docs/modules/ROOT/pages/traits.adoc
index 0c7ecc868..8bb33ba41 100644
--- a/docs/modules/ROOT/pages/traits.adoc
+++ b/docs/modules/ROOT/pages/traits.adoc
@@ -34,13 +34,13 @@ A trait may have additional properties that can be configured by the end user.
 E.g. the following command configures the container `port` that should be exposed by the service:
 
 ```
-kamel run --trait service.enabled=true --trait service.port=8081 file.groovy
+kamel run --trait service.enabled=true --trait container.service-port=8081 file.groovy
 ```
 
 Or the equivalent command (assuming that the service trait is enabled by auto-detection):
 
 ```
-kamel run -t service.port=8081 file.groovy
+kamel run -t container.service-port=8081 file.groovy
 ```
 
 NOTE: Enabling a trait does not force the trait to be activated, especially if the trait specific preconditions do not hold.
@@ -73,9 +73,6 @@ The following is a list of common traits that can be configured by the end users
 [cols="m,"]
 !===
 
-! deployer.container-image
-! Generates a container image for the Integration that includes the sources and resources in the generated images instead of mounting them to the pod.
-
 ! deployer.kind
 ! Allows to explicitly select the desired deployment kind between `deployment` or `knative-service` when creating the resources for running the integration.
 
@@ -190,18 +187,6 @@ More information can be found in the official Kubernetes documentation about htt
 [cols="m,"]
 !===
 
-! service.port
-! To configure a different port exposed by the service (default `80`).
-
-! service.port-name
-! To configure a different port name for the port exposed by the service (default `http`).
-
-! service.container-port
-! To configure a different port exposed by the container (default `8080`).
-
-! service.container-port-name
-! To configure a different port name for the port exposed by the container (default `http`).
-
 !===
 
 | route
@@ -326,7 +311,7 @@ It's disabled by default.
 
 | probes
 | Kubernetes, OpenShift
-| Configure LIveness and Readiness probes.
+| Configure Liveness and Readiness probes.
   +
   +
   It's disabled by default.
@@ -367,6 +352,42 @@ It's disabled by default.
 
 !===
 
+| container
+| All
+| Configure integration contianer.
+  +
+  +
+  It's disabled by default.
+
+[cols="m,"]
+!===
+
+! container.request-cpu
+! The minimum amount of CPU required.
+
+! container.request-memory
+! The minimum amount of memory required.
+
+! container.limit-cpu
+! The maximum amount of CPU required.
+
+! container.limit-memory
+! The maximum amount of memory required.
+
+! container.service-port
+! To configure under which service port the container port is to be exposed (default `80`).
+
+! container.service-port-name
+! To configure under which service port name the container port is to be exposed (default `http`).
+
+! container.port
+! To configure a different port exposed by the container (default `8080`).
+
+! container.port-name
+! To configure a different port name for the port exposed by the container (default `http`).
+
+!===
+
 |=======================
 
 
diff --git a/pkg/apis/camel/v1alpha1/integration_types.go b/pkg/apis/camel/v1alpha1/integration_types.go
index b6435afd0..618c85523 100644
--- a/pkg/apis/camel/v1alpha1/integration_types.go
+++ b/pkg/apis/camel/v1alpha1/integration_types.go
@@ -180,6 +180,10 @@ const (
 	IntegrationConditionKnativeServiceAvailable IntegrationConditionType = "KnativeServiceAvailable"
 	// IntegrationConditionExposureAvailable --
 	IntegrationConditionExposureAvailable IntegrationConditionType = "ExposureAvailable"
+	// IntegrationConditionPrometheusAvailable --
+	IntegrationConditionPrometheusAvailable IntegrationConditionType = "PrometheusAvailable"
+	// IntegrationConditionJolokiaAvailable --
+	IntegrationConditionJolokiaAvailable IntegrationConditionType = "JolokiaAvailable"
 
 	// IntegrationConditionKitAvailableReason --
 	IntegrationConditionKitAvailableReason string = "IntegrationKitAvailable"
@@ -193,6 +197,8 @@ const (
 	IntegrationConditionServiceAvailableReason string = "ServiceAvailable"
 	// IntegrationConditionServiceNotAvailableReason --
 	IntegrationConditionServiceNotAvailableReason string = "ServiceNotAvailable"
+	// IntegrationConditionContainerNotAvailableReason --
+	IntegrationConditionContainerNotAvailableReason string = "ContainerNotAvailable"
 	// IntegrationConditionRouteAvailableReason --
 	IntegrationConditionRouteAvailableReason string = "RouteAvailable"
 	// IntegrationConditionRouteNotAvailableReason --
@@ -205,6 +211,10 @@ const (
 	IntegrationConditionKnativeServiceAvailableReason string = "KnativeServiceAvailable"
 	// IntegrationConditionKnativeServiceNotAvailableReason --
 	IntegrationConditionKnativeServiceNotAvailableReason string = "KnativeServiceNotAvailable"
+	// IntegrationConditionPrometheusAvailableReason --
+	IntegrationConditionPrometheusAvailableReason string = "PrometheusAvailable"
+	// IntegrationConditionJolokiaAvailableReason --
+	IntegrationConditionJolokiaAvailableReason string = "JolokiaAvailable"
 )
 
 // IntegrationCondition describes the state of a resource at a certain point.
diff --git a/pkg/trait/classpath.go b/pkg/trait/classpath.go
index cb0eff981..d12585f73 100644
--- a/pkg/trait/classpath.go
+++ b/pkg/trait/classpath.go
@@ -97,9 +97,6 @@ func (t *classpathTrait) Apply(e *Environment) error {
 	}
 
 	if e.Resources != nil {
-		//
-		// Add mounted volumes as resources
-		//
 		e.Resources.VisitDeployment(func(deployment *appsv1.Deployment) {
 			for i := 0; i < len(deployment.Spec.Template.Spec.Containers); i++ {
 				cp := e.Classpath.Copy()
diff --git a/pkg/trait/container.go b/pkg/trait/container.go
index bfd29e748..118428a56 100644
--- a/pkg/trait/container.go
+++ b/pkg/trait/container.go
@@ -18,24 +18,38 @@ limitations under the License.
 package trait
 
 import (
+	"fmt"
+
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
+	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
 type containerTrait struct {
-	BaseTrait     `property:",squash"`
-	RequestCPU    string `property:"request-cpu"`
-	RequestMemory string `property:"request-memory"`
-	LimitCPU      string `property:"limit-cpu"`
-	LimitMemory   string `property:"limit-memory"`
+	BaseTrait `property:",squash"`
+
+	Auto            *bool  `property:"auto"`
+	RequestCPU      string `property:"request-cpu"`
+	RequestMemory   string `property:"request-memory"`
+	LimitCPU        string `property:"limit-cpu"`
+	LimitMemory     string `property:"limit-memory"`
+	Expose          *bool  `property:"expose"`
+	Port            int    `property:"port"`
+	PortName        string `property:"port-name"`
+	ServicePort     int    `property:"service-port"`
+	ServicePortName string `property:"service-port-name"`
 }
 
 func newContainerTrait() *containerTrait {
 	return &containerTrait{
-		BaseTrait: newBaseTrait("container"),
+		BaseTrait:       newBaseTrait("container"),
+		Port:            8080,
+		PortName:        httpPortName,
+		ServicePort:     80,
+		ServicePortName: httpPortName,
 	}
 }
 
@@ -44,7 +58,18 @@ func (t *containerTrait) Configure(e *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying), nil
+	if !e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) {
+		return false, nil
+	}
+
+	if t.Auto == nil || *t.Auto {
+		if t.Expose == nil {
+			e := e.Resources.GetServiceForIntegration(e.Integration) != nil
+			t.Expose = &e
+		}
+	}
+
+	return true, nil
 }
 
 func (t *containerTrait) Apply(e *Environment) error {
@@ -63,9 +88,55 @@ func (t *containerTrait) Apply(e *Environment) error {
 		})
 	}
 
+	if t.Expose != nil && *t.Expose {
+		t.configureService(e)
+	}
+
 	return nil
 }
 
+func (t *containerTrait) configureService(e *Environment) {
+	service := e.Resources.GetServiceForIntegration(e.Integration)
+	if service == nil {
+		return
+	}
+
+	container := e.Resources.GetContainerForIntegration(e.Integration)
+	if container == nil {
+		return
+	}
+
+	containerPort := corev1.ContainerPort{
+		Name:          t.PortName,
+		ContainerPort: int32(t.Port),
+		Protocol:      corev1.ProtocolTCP,
+	}
+
+	servicePort := corev1.ServicePort{
+		Name:       t.ServicePortName,
+		Port:       int32(t.ServicePort),
+		Protocol:   corev1.ProtocolTCP,
+		TargetPort: intstr.FromString(t.PortName),
+	}
+
+	e.Integration.Status.SetCondition(
+		v1alpha1.IntegrationConditionServiceAvailable,
+		corev1.ConditionTrue,
+		v1alpha1.IntegrationConditionServiceAvailableReason,
+
+		// service -> container
+		fmt.Sprintf("%s(%s/%d) -> %s(%s/%d)",
+			service.Name, servicePort.Name, servicePort.Port,
+			container.Name, containerPort.Name, containerPort.ContainerPort),
+	)
+
+	container.Ports = append(container.Ports, containerPort)
+	service.Spec.Ports = append(service.Spec.Ports, servicePort)
+
+	// Mark the service as a user service
+	service.Labels["camel.apache.org/service.type"] = v1alpha1.ServiceTypeUser
+}
+
 func (t *containerTrait) configureResources(_ *Environment, container *corev1.Container) {
 	//
 	// Requests
diff --git a/pkg/trait/jolokia.go b/pkg/trait/jolokia.go
index 3a81030f0..60dec4df4 100644
--- a/pkg/trait/jolokia.go
+++ b/pkg/trait/jolokia.go
@@ -18,7 +18,7 @@ limitations under the License.
 package trait
 
 import (
-	"errors"
+	"fmt"
 	"strconv"
 	"strings"
 
@@ -115,25 +115,33 @@ func (t *jolokiaTrait) Apply(e *Environment) (err error) {
 	}
 	envvar.SetVal(&e.EnvVars, "AB_JOLOKIA_OPTS", strings.Join(optionValues, ","))
 
-	// Register a post processor to add a container port to the integration deployment
-	e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
-		var container *corev1.Container
-		environment.Resources.VisitContainer(func(c *corev1.Container) {
-			if c.Name == environment.Integration.Name {
-				container = c
-			}
-		})
-		if container != nil {
-			container.Ports = append(container.Ports, corev1.ContainerPort{
-				Name:          "jolokia",
-				ContainerPort: int32(t.Port),
-				Protocol:      corev1.ProtocolTCP,
-			})
-		} else {
-			return errors.New("cannot add Jolokia container port: no integration container")
-		}
+	container := e.Resources.GetContainerForIntegration(e.Integration)
+	if container == nil {
+		e.Integration.Status.SetCondition(
+			v1alpha1.IntegrationConditionJolokiaAvailable,
+			corev1.ConditionFalse,
+			v1alpha1.IntegrationConditionContainerNotAvailableReason,
+			"",
+		)
+
 		return nil
-	})
+	}
+
+	containerPort := corev1.ContainerPort{
+		Name:          "jolokia",
+		ContainerPort: int32(t.Port),
+		Protocol:      corev1.ProtocolTCP,
+	}
+
+	e.Integration.Status.SetCondition(
+		v1alpha1.IntegrationConditionJolokiaAvailable,
+		corev1.ConditionTrue,
+		v1alpha1.IntegrationConditionJolokiaAvailableReason,
+		// service -> container
+		fmt.Sprintf("%s(%s/%d)", container.Name, containerPort.Name, containerPort.ContainerPort),
+	)
+
+	container.Ports = append(container.Ports, containerPort)
 
 	return nil
 }
diff --git a/pkg/trait/prometheus.go b/pkg/trait/prometheus.go
index 5229d7b9c..51cdbb5ab 100644
--- a/pkg/trait/prometheus.go
+++ b/pkg/trait/prometheus.go
@@ -18,7 +18,7 @@ limitations under the License.
 package trait
 
 import (
-	"errors"
+	"fmt"
 	"strconv"
 
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -66,43 +66,36 @@ func (t *prometheusTrait) Apply(e *Environment) (err error) {
 	// Configure the Prometheus Java agent
 	envvar.SetVal(&e.EnvVars, "AB_PROMETHEUS_PORT", strconv.Itoa(t.Port))
 
-	// Expose the Prometheus endpoint
-	// Either update the existing service added by previously executed traits
-	// (e.g. the service trait) or add a new service resource
-	svc := e.Resources.GetService(func(svc *corev1.Service) bool {
-		return svc.Name == e.Integration.Name
-	})
-	if svc == nil {
-		svc = getServiceFor(e)
-		e.Resources.Add(svc)
+	service, servicePort := t.configureServicePort(e)
+	container, containerPort := t.configureContainerPort(e)
+
+	condition := v1alpha1.IntegrationCondition{
+		Type:   v1alpha1.IntegrationConditionPrometheusAvailable,
+		Status: corev1.ConditionTrue,
+		Reason: v1alpha1.IntegrationConditionPrometheusAvailableReason,
 	}
-	port := corev1.ServicePort{
-		Name:       prometheusPortName,
-		Port:       int32(t.Port),
-		Protocol:   corev1.ProtocolTCP,
-		TargetPort: intstr.FromString(prometheusPortName),
+
+	if servicePort != nil {
+		service.Spec.Ports = append(service.Spec.Ports, *servicePort)
+		condition.Message = fmt.Sprintf("%s(%s/%d) -> ", service.Name, servicePort.Name, servicePort.Port)
+	} else {
+		condition.Status = corev1.ConditionFalse
+		condition.Reason = v1alpha1.IntegrationConditionServiceNotAvailableReason
 	}
-	svc.Spec.Ports = append(svc.Spec.Ports, port)
-
-	// Register a post processor to add a container port to the integration deployment
-	e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
-		var container *corev1.Container
-		environment.Resources.VisitContainer(func(c *corev1.Container) {
-			if c.Name == environment.Integration.Name {
-				container = c
-			}
-		})
-		if container != nil {
-			container.Ports = append(container.Ports, corev1.ContainerPort{
-				Name:          prometheusPortName,
-				ContainerPort: int32(t.Port),
-				Protocol:      corev1.ProtocolTCP,
-			})
-		} else {
-			return errors.New("cannot add Prometheus container port: no integration container")
-		}
+
+	if containerPort != nil {
+		container.Ports = append(container.Ports, *containerPort)
+		condition.Message += fmt.Sprintf("%s(%s/%d)", container.Name, containerPort.Name, containerPort.ContainerPort)
+	} else {
+		condition.Status = corev1.ConditionFalse
+		condition.Reason = v1alpha1.IntegrationConditionContainerNotAvailableReason
+	}
+
+	e.Integration.Status.SetConditions(condition)
+
+	if condition.Status == corev1.ConditionFalse {
 		return nil
-	})
+	}
 
 	if t.ServiceMonitor {
 		// Add the ServiceMonitor resource
@@ -116,6 +109,37 @@ func (t *prometheusTrait) Apply(e *Environment) (err error) {
 	return nil
 }
 
+func (t *prometheusTrait) configureContainerPort(e *Environment) (*corev1.Container, *corev1.ContainerPort) {
+	container := e.Resources.GetContainerForIntegration(e.Integration)
+	if container == nil {
+		return nil, nil
+	}
+
+	containerPort := corev1.ContainerPort{
+		Name:          prometheusPortName,
+		ContainerPort: int32(t.Port),
+		Protocol:      corev1.ProtocolTCP,
+	}
+
+	return container, &containerPort
+}
+
+func (t *prometheusTrait) configureServicePort(e *Environment) (*corev1.Service, *corev1.ServicePort) {
+	service := e.Resources.GetServiceForIntegration(e.Integration)
+	if service == nil {
+		return nil, nil
+	}
+
+	servicePort := corev1.ServicePort{
+		Name:       prometheusPortName,
+		Port:       int32(t.Port),
+		Protocol:   corev1.ProtocolTCP,
+		TargetPort: intstr.FromString(prometheusPortName),
+	}
+
+	return service, &servicePort
+}
+
 func (t *prometheusTrait) getServiceMonitorFor(e *Environment) (*monitoringv1.ServiceMonitor, error) {
 	labels, err := parseCsvMap(&t.ServiceMonitorLabels)
 	if err != nil {
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 3bcbcc44f..ae204c035 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -18,35 +18,23 @@ limitations under the License.
 package trait
 
 import (
-	"fmt"
-
 	"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
 	"github.com/apache/camel-k/pkg/metadata"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
 )
 
 type serviceTrait struct {
 	BaseTrait `property:",squash"`
-
-	Auto              *bool  `property:"auto"`
-	Port              int    `property:"port"`
-	PortName          string `property:"port-name"`
-	ContainerPort     int    `property:"container-port"`
-	ContainerPortName string `property:"container-port-name"`
+	Auto      *bool `property:"auto"`
 }
 
 const httpPortName = "http"
 
 func newServiceTrait() *serviceTrait {
 	return &serviceTrait{
-		BaseTrait:         newBaseTrait("service"),
-		Port:              80,
-		PortName:          httpPortName,
-		ContainerPort:     8080,
-		ContainerPortName: httpPortName,
+		BaseTrait: newBaseTrait("service"),
 	}
 }
 
@@ -96,79 +84,31 @@ func (t *serviceTrait) Configure(e *Environment) (bool, error) {
 }
 
 func (t *serviceTrait) Apply(e *Environment) (err error) {
-	// Either update the existing service added by previously executed traits
-	// (e.g. the prometheus trait) or add a new service resource
-	svc := e.Resources.GetService(func(svc *corev1.Service) bool {
-		return svc.Name == e.Integration.Name
-	})
+	svc := e.Resources.GetServiceForIntegration(e.Integration)
 	if svc == nil {
-		svc = getServiceFor(e)
-		e.Resources.Add(svc)
-	}
-	port := corev1.ServicePort{
-		Name:       t.PortName,
-		Port:       int32(t.Port),
-		Protocol:   corev1.ProtocolTCP,
-		TargetPort: intstr.FromString(t.ContainerPortName),
-	}
-	svc.Spec.Ports = append(svc.Spec.Ports, port)
-
-	// Mark the service as a user service
-	svc.Labels["camel.apache.org/service.type"] = v1alpha1.ServiceTypeUser
-
-	// Register a post processor to add a container port to the integration deployment
-	e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
-		container := environment.Resources.GetContainer(func(c *corev1.Container) bool {
-			return c.Name == environment.Integration.Name
-		})
-
-		if container != nil {
-			container.Ports = append(container.Ports, corev1.ContainerPort{
-				Name:          t.ContainerPortName,
-				ContainerPort: int32(t.ContainerPort),
-				Protocol:      corev1.ProtocolTCP,
-			})
-
-			message := fmt.Sprintf("%s(%s/%d) -> %s(%s/%d)",
-				svc.Name, port.Name, port.Port,
-				container.Name, t.ContainerPortName, t.ContainerPort,
-			)
-
-			environment.Integration.Status.SetCondition(
-				v1alpha1.IntegrationConditionServiceAvailable,
-				corev1.ConditionTrue,
-				v1alpha1.IntegrationConditionServiceAvailableReason,
-				message,
-			)
-		} else {
-			return fmt.Errorf("cannot add %s container port: no integration container", t.ContainerPortName)
-		}
-		return nil
-	})
-
-	return nil
-}
-
-func getServiceFor(e *Environment) *corev1.Service {
-	svc := corev1.Service{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Service",
-			APIVersion: "v1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      e.Integration.Name,
-			Namespace: e.Integration.Namespace,
-			Labels: map[string]string{
-				"camel.apache.org/integration": e.Integration.Name,
+		svc := corev1.Service{
+			TypeMeta: metav1.TypeMeta{
+				Kind:       "Service",
+				APIVersion: "v1",
 			},
-		},
-		Spec: corev1.ServiceSpec{
-			Ports: []corev1.ServicePort{},
-			Selector: map[string]string{
-				"camel.apache.org/integration": e.Integration.Name,
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      e.Integration.Name,
+				Namespace: e.Integration.Namespace,
+				Labels: map[string]string{
+					"camel.apache.org/integration": e.Integration.Name,
+				},
 			},
-		},
+			Spec: corev1.ServiceSpec{
+				Ports: []corev1.ServicePort{},
+				Selector: map[string]string{
+					"camel.apache.org/integration": e.Integration.Name,
+				},
+			},
+		}
+
+		// add a new service if not already created
+		e.Resources.Add(&svc)
 	}
 
-	return &svc
+	return nil
 }
diff --git a/pkg/trait/trait_catalog.go b/pkg/trait/trait_catalog.go
index ffb2b794a..b6a993888 100644
--- a/pkg/trait/trait_catalog.go
+++ b/pkg/trait/trait_catalog.go
@@ -134,15 +134,15 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 			c.tDependencies,
 			c.tBuilder,
 			c.tEnvironment,
-			c.tJolokia,
-			c.tPrometheus,
 			c.tDeployer,
 			c.tDeployment,
 			c.tAffinity,
+			c.tService,
 			c.tContainer,
+			c.tJolokia,
+			c.tPrometheus,
 			c.tClasspath,
 			c.tProbes,
-			c.tService,
 			c.tRoute,
 			c.tOwner,
 		}
@@ -155,15 +155,15 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait {
 			c.tDependencies,
 			c.tBuilder,
 			c.tEnvironment,
-			c.tJolokia,
-			c.tPrometheus,
 			c.tDeployer,
 			c.tDeployment,
 			c.tAffinity,
+			c.tService,
 			c.tContainer,
+			c.tJolokia,
+			c.tPrometheus,
 			c.tClasspath,
 			c.tProbes,
-			c.tService,
 			c.tIngress,
 			c.tOwner,
 		}
diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go
index 418b2db93..ca27e9a69 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -198,6 +198,16 @@ func (c *Collection) GetUserServiceForIntegration(integration *v1alpha1.Integrat
 	})
 }
 
+// GetServiceForIntegration returns a user Service for the given integration
+func (c *Collection) GetServiceForIntegration(integration *v1alpha1.Integration) *corev1.Service {
+	if integration == nil {
+		return nil
+	}
+	return c.GetService(func(s *corev1.Service) bool {
+		return s.ObjectMeta.Labels != nil && s.ObjectMeta.Labels["camel.apache.org/integration"] == integration.Name
+	})
+}
+
 // GetKnativeService returns a knative Service that matches the given function
 func (c *Collection) GetKnativeService(filter func(*serving.Service) bool) *serving.Service {
 	var retValue *serving.Service
@@ -251,6 +261,17 @@ func (c *Collection) GetContainer(filter func(container *corev1.Container) bool)
 	return retValue
 }
 
+// GetContainerForIntegration --
+func (c *Collection) GetContainerForIntegration(integration *v1alpha1.Integration) *corev1.Container {
+	if integration == nil {
+		return nil
+	}
+
+	return c.GetContainer(func(c *corev1.Container) bool {
+		return c.Name == integration.Name
+	})
+}
+
 // VisitContainer executes the visitor function on all Containers inside deployments or other resources
 func (c *Collection) VisitContainer(visitor func(container *corev1.Container)) {
 	c.VisitDeployment(func(d *appsv1.Deployment) {


Test patch:
diff --git a/pkg/trait/service_test.go b/pkg/trait/service_test.go
index ba88353be..4e14f66ff 100644
--- a/pkg/trait/service_test.go
+++ b/pkg/trait/service_test.go
@@ -94,7 +94,7 @@ func TestServiceWithDefaults(t *testing.T) {
 		},
 		EnvVars:        make([]corev1.EnvVar, 0),
 		ExecutedTraits: make([]Trait, 0),
-		Resources:      kubernetes.NewCollection(&appsv1.Deployment{}),
+		Resources:      kubernetes.NewCollection(),
 		Classpath:      strset.New(),
 	}
 
@@ -104,6 +104,7 @@ func TestServiceWithDefaults(t *testing.T) {
 	assert.NotEmpty(t, environment.ExecutedTraits)
 	assert.NotNil(t, environment.GetTrait(ID("deployment")))
 	assert.NotNil(t, environment.GetTrait(ID("service")))
+	assert.NotNil(t, environment.GetTrait(ID("container")))
 
 	s := environment.Resources.GetService(func(service *corev1.Service) bool {
 		return service.Name == ServiceTestName
@@ -158,12 +159,18 @@ func TestService(t *testing.T) {
 				Traits: map[string]v1alpha1.TraitSpec{
 					"service": {
 						Configuration: map[string]string{
-							"enabled":             "true",
-							"auto":                "false",
-							"port":                "81",
-							"port-name":           "http-81",
-							"container-port":      "8081",
-							"container-port-name": "http-8081",
+							"enabled": "true",
+						},
+					},
+					"container": {
+						Configuration: map[string]string{
+							"enabled":           "true",
+							"auto":              "false",
+							"expose":            "true",
+							"port":              "8081",
+							"port-name":         "http-8081",
+							"service-port":      "81",
+							"service-port-name": "http-81",
 						},
 					},
 				},
@@ -185,7 +192,7 @@ func TestService(t *testing.T) {
 		},
 		EnvVars:        make([]corev1.EnvVar, 0),
 		ExecutedTraits: make([]Trait, 0),
-		Resources:      kubernetes.NewCollection(&appsv1.Deployment{}),
+		Resources:      kubernetes.NewCollection(),
 		Classpath:      strset.New(),
 	}
 
@@ -195,6 +202,7 @@ func TestService(t *testing.T) {
 	assert.NotEmpty(t, environment.ExecutedTraits)
 	assert.NotNil(t, environment.GetTrait(ID("deployment")))
 	assert.NotNil(t, environment.GetTrait(ID("service")))
+	assert.NotNil(t, environment.GetTrait(ID("container")))
 
 	s := environment.Resources.GetService(func(service *corev1.Service) bool {
 		return service.Name == ServiceTestName
diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go
index 797f2ed71..44ddda69f 100644
--- a/pkg/trait/trait_test.go
+++ b/pkg/trait/trait_test.go
@@ -156,13 +156,13 @@ func TestTraitDecode(t *testing.T) {
 	}
 	env.Integration.Spec.Traits["service"] = svcTrait
 
-	svc := newServiceTrait()
-	err := decodeTraitSpec(&svcTrait, svc)
+	ctr := newContainerTrait()
+	err := decodeTraitSpec(&svcTrait, ctr)
 
 	assert.Nil(t, err)
-	assert.Equal(t, 7071, svc.Port)
-	assert.NotNil(t, svc.Enabled)
-	assert.Equal(t, false, *svc.Enabled)
+	assert.Equal(t, 7071, ctr.Port)
+	assert.NotNil(t, ctr.Enabled)
+	assert.Equal(t, false, *ctr.Enabled)
 }
 
 func TestTraitHierarchyDecode(t *testing.T) {