You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2021/02/09 12:32:02 UTC

[camel-k] 01/06: feat: Add ServiceBinding trait (#1445)

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

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

commit a000ec5c18b0e504ccd2e424a3ca417e0d50b11a
Author: John Poth <po...@gmail.com>
AuthorDate: Fri Sep 25 14:31:30 2020 +0200

    feat: Add ServiceBinding trait (#1445)
---
 deploy/operator-role-binding-service-binding.yaml  |  30 +++
 deploy/operator-role-service-binding.yaml          |  37 +++
 go.mod                                             |   7 +-
 pkg/apis/addtoscheme_service_binding.go            |  25 ++
 pkg/apis/camel/v1/integration_types.go             |   4 +
 pkg/cmd/run.go                                     |   9 +-
 .../integration/integration_controller.go          |  10 +
 pkg/controller/integration/platform_setup.go       |   3 +-
 pkg/trait/deployer.go                              |   3 +-
 pkg/trait/service_binding.go                       | 266 +++++++++++++++++++++
 pkg/trait/trait_register.go                        |   1 +
 pkg/trait/trait_types.go                           |  33 ++-
 12 files changed, 418 insertions(+), 10 deletions(-)

diff --git a/deploy/operator-role-binding-service-binding.yaml b/deploy/operator-role-binding-service-binding.yaml
new file mode 100644
index 0000000..9a2621a
--- /dev/null
+++ b/deploy/operator-role-binding-service-binding.yaml
@@ -0,0 +1,30 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: camel-k-operator-service-binding
+  labels:
+    app: "camel-k"
+subjects:
+- kind: ServiceAccount
+  name: camel-k-operator
+roleRef:
+  kind: Role
+  name: camel-k-operator-service-binding
+  apiGroup: rbac.authorization.k8s.io
diff --git a/deploy/operator-role-service-binding.yaml b/deploy/operator-role-service-binding.yaml
new file mode 100644
index 0000000..68a2774
--- /dev/null
+++ b/deploy/operator-role-service-binding.yaml
@@ -0,0 +1,37 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+kind: Role
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: camel-k-operator-service-binding
+  labels:
+    app: "camel-k"
+rules:
+- apiGroups:
+    - operators.coreos.com
+  resources:
+    - servicebindings
+  verbs:
+  - create
+  - delete
+  - deletecollection
+  - get
+  - list
+  - patch
+  - update
+  - watch
diff --git a/go.mod b/go.mod
index e744805..603cb68 100644
--- a/go.mod
+++ b/go.mod
@@ -28,6 +28,7 @@ require (
 	github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.42.1
 	github.com/prometheus/client_golang v1.7.1
 	github.com/radovskyb/watcher v1.0.6
+	github.com/redhat-developer/service-binding-operator v0.4.0
 	github.com/rs/xid v1.2.1
 	github.com/scylladb/go-set v1.0.2
 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
@@ -37,7 +38,7 @@ require (
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.6.2
 	github.com/stoewer/go-strcase v1.0.2
-	github.com/stretchr/testify v1.5.1
+	github.com/stretchr/testify v1.6.1
 	go.uber.org/multierr v1.5.0
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
 	gopkg.in/inf.v0 v0.9.1
@@ -57,6 +58,10 @@ replace (
 	github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible // Required by OLM
 	k8s.io/client-go => k8s.io/client-go v0.18.9
 	k8s.io/code-generator => k8s.io/code-generator v0.18.9
+	// Required by Service Binding Operator dependency, see https://github.com/redhat-developer/service-binding-operator/pull/798
+	github.com/operator-framework/operator-lifecycle-manager => github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200321030439-57b580e57e88
+	github.com/operator-framework/operator-sdk => github.com/operator-framework/operator-sdk v0.17.1
+	k8s.io/api => k8s.io/api v0.18.9
 )
 
 replace github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 // Required by Helm
diff --git a/pkg/apis/addtoscheme_service_binding.go b/pkg/apis/addtoscheme_service_binding.go
new file mode 100644
index 0000000..9fc4a7b
--- /dev/null
+++ b/pkg/apis/addtoscheme_service_binding.go
@@ -0,0 +1,25 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package apis
+
+import "github.com/redhat-developer/service-binding-operator/pkg/apis/operators/v1alpha1"
+
+func init() {
+	// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
+	AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme)
+}
diff --git a/pkg/apis/camel/v1/integration_types.go b/pkg/apis/camel/v1/integration_types.go
index 3d103d5..5a9e631 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -112,6 +112,8 @@ const (
 	IntegrationPhaseBuildingKit IntegrationPhase = "Building Kit"
 	// IntegrationPhaseResolvingKit --
 	IntegrationPhaseResolvingKit IntegrationPhase = "Resolving Kit"
+	// IntegrationPhaseWaitingForServiceBindingCollectionReady --
+	IntegrationPhaseWaitingForServiceBindingCollectionReady IntegrationPhase = "Waiting For Service binding Collection Ready Status"
 	// IntegrationPhaseDeploying --
 	IntegrationPhaseDeploying IntegrationPhase = "Deploying"
 	// IntegrationPhaseRunning --
@@ -133,6 +135,8 @@ const (
 	IntegrationConditionKnativeServiceAvailable IntegrationConditionType = "KnativeServiceAvailable"
 	// IntegrationConditionCronJobAvailable --
 	IntegrationConditionCronJobAvailable IntegrationConditionType = "CronJobAvailable"
+	// IntegrationConditionServiceBindingCollectionReady --
+	IntegrationConditionServiceBindingCollectionReady IntegrationConditionType = "ServiceBindingCollectionReady"
 	// IntegrationConditionExposureAvailable --
 	IntegrationConditionExposureAvailable IntegrationConditionType = "ExposureAvailable"
 	// IntegrationConditionPrometheusAvailable --
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 874c443..6f5f4ce 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -72,6 +72,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions)
 	}
 
 	cmd.Flags().String("name", "", "The integration name")
+	cmd.Flags().StringArrayP("connect", "c", nil, "A ServiceBinding or Provisioned Service that the integration should bind to specified as KIND.VERSION.GROUP/NAME[/NAMESPACE]")
 	cmd.Flags().StringArrayP("dependency", "d", nil, "An external library that should be included. E.g. for Maven dependencies \"mvn:org.my/app:1.0\"")
 	cmd.Flags().BoolP("wait", "w", false, "Wait for the integration to be running")
 	cmd.Flags().StringP("kit", "k", "", "The kit used to run the integration")
@@ -117,6 +118,7 @@ type runCmdOptions struct {
 	IntegrationName string   `mapstructure:"name" yaml:",omitempty"`
 	Profile         string   `mapstructure:"profile" yaml:",omitempty"`
 	OutputFormat    string   `mapstructure:"output" yaml:",omitempty"`
+	Connects        []string `mapstructure:"connects" yaml:",omitempty"`
 	Resources       []string `mapstructure:"resources" yaml:",omitempty"`
 	OpenAPIs        []string `mapstructure:"open-apis" yaml:",omitempty"`
 	Dependencies    []string `mapstructure:"dependencies" yaml:",omitempty"`
@@ -658,7 +660,12 @@ func (o *runCmdOptions) GetIntegrationName(sources []string) string {
 	return name
 }
 
-func (*runCmdOptions) configureTraits(integration *v1.Integration, options []string, catalog *trait.Catalog) error {
+func (o *runCmdOptions) configureTraits(integration *v1.Integration, options []string, catalog *trait.Catalog) error {
+	// configure ServiceBinding trait
+	for _, sb := range o.Connects {
+		bindings := fmt.Sprintf("service-binding.service-bindings=%s", sb)
+		options = append(options, bindings)
+	}
 	traits, err := configureTraits(options, catalog)
 	if err != nil {
 		return err
diff --git a/pkg/controller/integration/integration_controller.go b/pkg/controller/integration/integration_controller.go
index 5a1b5fe..012a20c 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -44,6 +44,7 @@ import (
 	"github.com/apache/camel-k/pkg/util/digest"
 	"github.com/apache/camel-k/pkg/util/log"
 	"github.com/apache/camel-k/pkg/util/monitoring"
+    sb "github.com/redhat-developer/service-binding-operator/pkg/apis/operators/v1alpha1"
 )
 
 // Add creates a new Integration Controller and adds it to the Manager. The Manager will set fields on the Controller
@@ -217,6 +218,15 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
 		return err
 	}
 
+	// Watch ServiceBindings created
+	err = c.Watch(&source.Kind{Type: &sb.ServiceBinding{}}, &handler.EnqueueRequestForOwner{
+		OwnerType:    &v1.Integration{},
+		IsController: true,
+	})
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/pkg/controller/integration/platform_setup.go b/pkg/controller/integration/platform_setup.go
index 51c5a5b..0bf4127 100644
--- a/pkg/controller/integration/platform_setup.go
+++ b/pkg/controller/integration/platform_setup.go
@@ -46,7 +46,8 @@ func (action *platformSetupAction) Name() string {
 // CanHandle tells whether this action can handle the integration
 func (action *platformSetupAction) CanHandle(integration *v1.Integration) bool {
 	return integration.Status.Phase == v1.IntegrationPhaseNone ||
-		integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform
+		integration.Status.Phase == v1.IntegrationPhaseWaitingForPlatform ||
+		integration.Status.Phase == v1.IntegrationPhaseWaitingForServiceBindingCollectionReady
 }
 
 // Handle handles the integrations
diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
index 81aaee8..ed11cf4 100644
--- a/pkg/trait/deployer.go
+++ b/pkg/trait/deployer.go
@@ -49,6 +49,7 @@ func newDeployerTrait() Trait {
 func (t *deployerTrait) Configure(e *Environment) (bool, error) {
 	return e.IntegrationInPhase(
 		v1.IntegrationPhaseNone,
+		v1.IntegrationPhaseWaitingForServiceBindingCollectionReady,
 		v1.IntegrationPhaseWaitingForPlatform,
 		v1.IntegrationPhaseInitialization,
 		v1.IntegrationPhaseBuildingKit,
@@ -79,7 +80,7 @@ func (t *deployerTrait) Apply(e *Environment) error {
 			})
 		}
 
-	case v1.IntegrationPhaseRunning:
+	case v1.IntegrationPhaseRunning, v1.IntegrationPhaseWaitingForServiceBindingCollectionReady:
 		// Register a post action that patches the resources generated by the traits
 		e.PostActions = append(e.PostActions, func(env *Environment) error {
 			for _, resource := range env.Resources.Items() {
diff --git a/pkg/trait/service_binding.go b/pkg/trait/service_binding.go
new file mode 100644
index 0000000..131d2c5
--- /dev/null
+++ b/pkg/trait/service_binding.go
@@ -0,0 +1,266 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package trait
+
+import (
+	"fmt"
+	"strings"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	sb "github.com/redhat-developer/service-binding-operator/pkg/apis/operators/v1alpha1"
+	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// The Service Binding trait allows users to connect to Provisioned Services and ServiceBindings in Kubernetes:
+// https://github.com/k8s-service-bindings/spec#service-binding
+// As the specification is still evolving this is subject to change
+// +camel-k:trait=service-binding
+type serviceBindingTrait struct {
+	BaseTrait `property:",squash"`
+	// List of Provisioned Services and ServiceBindings in the form KIND.VERSION.GROUP/NAME[/NAMESPACE]
+	ServiceBindings []string `property:"service-bindings" json:"serviceBindings,omitempty"`
+}
+
+func newServiceBindingTrait() Trait {
+	return &serviceBindingTrait{
+		BaseTrait: NewBaseTrait("service-binding", 250),
+	}
+}
+
+// IsAllowedInProfile overrides default
+func (t *serviceBindingTrait) IsAllowedInProfile(profile v1.TraitProfile) bool {
+	return profile == v1.TraitProfileKubernetes ||
+		profile == v1.TraitProfileOpenShift
+}
+
+func (t *serviceBindingTrait) Configure(e *Environment) (bool, error) {
+	if t.Enabled != nil && !*t.Enabled {
+		return false, nil
+	}
+
+	if len(t.ServiceBindings) == 0 {
+		return false, nil
+	}
+
+	return e.IntegrationInPhase(
+		v1.IntegrationPhaseInitialization,
+		v1.IntegrationPhaseWaitingForServiceBindingCollectionReady,
+		v1.IntegrationPhaseDeploying,
+		v1.IntegrationPhaseRunning,
+	), nil
+}
+
+func (t *serviceBindingTrait) Apply(e *Environment) error {
+	integrationServiceBindingName := e.Integration.Name + "-service-binding-request"
+	services, err := t.parseProvisionedServices(e)
+	if err != nil {
+		return err
+	}
+	serviceBindings, err := t.parseServiceBindings(e)
+	if err != nil {
+		return err
+	}
+	if len(services) > 0 {
+		serviceBindings = append(serviceBindings, integrationServiceBindingName)
+	}
+	if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
+		serviceBindingsCollectionReady := true
+		for _, name := range serviceBindings {
+			isIntSB := name == integrationServiceBindingName
+			serviceBinding, err := t.getServiceBinding(e, name)
+			// Do not throw an error if the ServiceBinding is not found and if we are managing it: we will create it
+			if (err != nil && !k8serrors.IsNotFound(err)) || (err != nil && !isIntSB) {
+				return err
+			}
+			if isIntSB {
+				request := createServiceBinding(e, services, integrationServiceBindingName)
+				e.Resources.Add(&request)
+			}
+			if isCollectionReady(serviceBinding) {
+				setCollectionReady(e, name, corev1.ConditionTrue)
+			} else {
+				setCollectionReady(e, name, corev1.ConditionFalse)
+				serviceBindingsCollectionReady = false
+			}
+		}
+		if !serviceBindingsCollectionReady {
+			e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
+				e.Integration.Status.Phase = v1.IntegrationPhaseWaitingForServiceBindingCollectionReady
+				return nil
+			})
+		}
+		return nil
+	} else if e.IntegrationInPhase(v1.IntegrationPhaseWaitingForServiceBindingCollectionReady) {
+		for _, name := range serviceBindings {
+			serviceBinding, err := t.getServiceBinding(e, name)
+			if err != nil {
+				return err
+			}
+			if isCollectionReady(serviceBinding) {
+				setCollectionReady(e, name, corev1.ConditionTrue)
+			} else {
+				setCollectionReady(e, name, corev1.ConditionFalse)
+				return nil
+			}
+		}
+		e.PostActions = append(e.PostActions, func(environment *Environment) error {
+			e.Integration.Status.Phase = v1.IntegrationPhaseInitialization
+			return nil
+		})
+	} else if e.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning) {
+		e.ServiceBindings = make(map[string]string)
+		for _, name := range serviceBindings {
+			sb, err := t.getServiceBinding(e, name)
+			if err != nil {
+				return err
+			}
+			if !isCollectionReady(sb) {
+				setCollectionReady(e, name, corev1.ConditionFalse)
+				e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
+					e.Integration.Status.Phase = v1.IntegrationPhaseWaitingForServiceBindingCollectionReady
+					return nil
+				})
+				return nil
+			}
+			e.ServiceBindings[name] = sb.Status.Secret
+			if name == integrationServiceBindingName {
+				request := createServiceBinding(e, services, integrationServiceBindingName)
+				e.Resources.Add(&request)
+			}
+		}
+		e.ApplicationProperties["quarkus.kubernetes-service-binding.enabled"] = "true"
+		e.ApplicationProperties["SERVICE_BINDING_ROOT"] = ServiceBindingsMountPath
+	}
+	return nil
+}
+
+func setCollectionReady(e *Environment, serviceBinding string, status corev1.ConditionStatus) {
+	e.Integration.Status.SetCondition(
+		v1.IntegrationConditionServiceBindingCollectionReady,
+		status,
+		"",
+		fmt.Sprintf("Name=%s", serviceBinding),
+	)
+}
+
+func isCollectionReady(sb sb.ServiceBinding) bool {
+	for _, condition := range sb.Status.Conditions {
+		if condition.Type == "CollectionReady" {
+			return condition.Status == corev1.ConditionTrue && sb.Status.Secret != ""
+		}
+	}
+	return false
+}
+
+func (t *serviceBindingTrait) getServiceBinding(e *Environment, name string) (sb.ServiceBinding, error) {
+	serviceBinding := sb.ServiceBinding{}
+	key := k8sclient.ObjectKey{
+		Namespace: e.Integration.Namespace,
+		Name:      name,
+	}
+	return serviceBinding, t.Client.Get(t.Ctx, key, &serviceBinding)
+}
+
+func (t *serviceBindingTrait) parseProvisionedServices(e *Environment) ([]sb.Service, error) {
+	services := make([]sb.Service, 0)
+	for _, s := range t.ServiceBindings {
+		seg := strings.Split(s, "/")
+		if !(len(seg) == 3 || len(seg) == 2) {
+			return nil, fmt.Errorf("ServiceBinding: %s should be specified in the form KIND.VERSION.GROUP/NAME[/NAMESPACE]", s)
+		}
+		gvk := seg[0]
+		index := strings.Index(gvk, ".")
+		kind := seg[0][0:index]
+		if kind == "ServiceBinding" {
+			continue
+		}
+		vg := seg[0][index+1 : len(gvk)]
+		index = strings.Index(vg, ".")
+		version := vg[0:index]
+		group := vg[index+1 : len(vg)]
+		name := seg[1]
+		namespace := e.Integration.Namespace
+		if len(seg) == 3 {
+			namespace = seg[2]
+		}
+		namePrefix := ""
+		service := sb.Service{
+			GroupVersionKind: metav1.GroupVersionKind{
+				Group:   group,
+				Version: version,
+				Kind:    kind,
+			},
+			EnvVarPrefix: &namePrefix,
+			LocalObjectReference: corev1.LocalObjectReference{
+				Name: name,
+			},
+			Namespace: &namespace,
+		}
+		services = append(services, service)
+	}
+	return services, nil
+}
+
+func (t *serviceBindingTrait) parseServiceBindings(e *Environment) ([]string, error) {
+	serviceBindings := make([]string, 0)
+	for _, s := range t.ServiceBindings {
+		seg := strings.Split(s, "/")
+		if !(len(seg) == 3 || len(seg) == 2) {
+			return nil, fmt.Errorf("ServiceBinding: %s should be specified in the form KIND.VERSION.GROUP/NAME[/NAMESPACE]", s)
+		}
+		gvk := seg[0]
+		index := strings.Index(gvk, ".")
+		kind := seg[0][0:index]
+		if kind == "ServiceBinding" {
+			vg := seg[0][index+1 : len(gvk)]
+			if vg != "v1alpha1.operators.coreos.com" {
+				return nil, fmt.Errorf("ServiceBinding: %s VERSION.GROUP should be v1alpha1.operators.coreos.com", s)
+			}
+			if len(seg) == 3 && seg[2] != e.Integration.Namespace {
+				return nil, fmt.Errorf("ServiceBinding: %s should be in the same namespace %s as the integration", s, e.Integration.Namespace)
+			}
+			serviceBindings = append(serviceBindings, seg[1])
+		}
+	}
+	return serviceBindings, nil
+}
+
+func createServiceBinding(e *Environment, services []sb.Service, name string) sb.ServiceBinding {
+	spec := sb.ServiceBindingSpec{
+		Services: services,
+	}
+	labels := map[string]string{
+		v1.IntegrationLabel: e.Integration.Name,
+	}
+	serviceBinding := sb.ServiceBinding{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ServiceBinding",
+			APIVersion: "operators.coreos.com/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: e.Integration.Namespace,
+			Name:      name,
+			Labels:    labels,
+		},
+		Spec: spec,
+	}
+	return serviceBinding
+}
diff --git a/pkg/trait/trait_register.go b/pkg/trait/trait_register.go
index ef171c7..1ec4e31 100644
--- a/pkg/trait/trait_register.go
+++ b/pkg/trait/trait_register.go
@@ -45,6 +45,7 @@ func init() {
 	AddToTraits(newRouteTrait)
 	AddToTraits(newIstioTrait)
 	AddToTraits(newIngressTrait)
+	AddToTraits(newServiceBindingTrait)
 	AddToTraits(newOwnerTrait)
 	AddToTraits(newPdbTrait)
 }
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 7b60392..ed893e0 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -42,12 +42,13 @@ import (
 const True = "true"
 
 var (
-	basePath            = "/etc/camel"
-	confDPath           = path.Join(basePath, "conf.d")
-	sourcesMountPath    = path.Join(basePath, "sources")
-	resourcesMountPath  = path.Join(basePath, "resources")
-	configMapsMountPath = path.Join(confDPath, "_configmaps")
-	secretsMountPath    = path.Join(confDPath, "_secrets")
+	basePath                 = "/etc/camel"
+	confDPath                = path.Join(basePath, "conf.d")
+	sourcesMountPath         = path.Join(basePath, "sources")
+	resourcesMountPath       = path.Join(basePath, "resources")
+	configMapsMountPath      = path.Join(confDPath, "_configmaps")
+	secretsMountPath         = path.Join(confDPath, "_secrets")
+	serviceBindingsMountPath = path.Join(ConfdPath, "_servicebindings")
 )
 
 // Identifiable represent an identifiable type
@@ -196,6 +197,7 @@ type Environment struct {
 	EnvVars               []corev1.EnvVar
 	ApplicationProperties map[string]string
 	Interceptors          []string
+	ServiceBindings       map[string]string
 }
 
 // ControllerStrategy is used to determine the kind of controller that needs to be created for the integration
@@ -671,6 +673,25 @@ func (e *Environment) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]c
 	//
 	// Volumes :: Additional Secrets
 	//
+    // append Service Binding secrets
+    for _, name := range e.ServiceBindings {
+        refName := kubernetes.SanitizeLabel(name)
+
+        *vols = append(*vols, corev1.Volume{
+            Name: refName,
+            VolumeSource: corev1.VolumeSource{
+                Secret: &corev1.SecretVolumeSource{
+                    SecretName: name,
+                },
+            },
+        })
+
+        *mnts = append(*mnts, corev1.VolumeMount{
+            Name:      refName,
+            MountPath: path.Join(serviceBindingsMountPath, strings.ToLower(name)),
+        })
+    }
+    secrets := e.CollectConfigurationValues("secret")
 	for _, secretName := range e.collectConfigurationValues("secret") {
 		refName := kubernetes.SanitizeLabel(secretName)