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

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

commit 68df2fb0aa8668ef46e89e5c6be1381d595e7e28
Author: lburgazzoli <lburgazz...@gmail.com>
AuthorDate: Fri Jun 21 13:12:05 2019 +0200

    Add conditions to camel-k CRs #594
---
 pkg/apis/camel/v1alpha1/build_types.go             |  46 +++++--
 pkg/apis/camel/v1alpha1/build_types_support.go     |  92 ++++++++++++-
 pkg/apis/camel/v1alpha1/common_types.go            |  10 ++
 pkg/apis/camel/v1alpha1/integration_types.go       |  82 ++++++++++--
 .../camel/v1alpha1/integration_types_support.go    | 147 +++++++++++++++++----
 pkg/apis/camel/v1alpha1/integrationkit_types.go    |  47 +++++--
 .../camel/v1alpha1/integrationkit_types_support.go |  97 +++++++++++++-
 .../camel/v1alpha1/integrationplatform_types.go    |  25 +++-
 .../v1alpha1/integrationplatform_types_support.go  |  77 +++++++++++
 pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go   | 102 +++++++++++++-
 pkg/cmd/describe_integration.go                    |  48 ++++++-
 pkg/controller/build/build_controller.go           |   6 +-
 pkg/controller/integration/build_kit.go            |  21 +--
 pkg/controller/integration/deploy.go               |   3 +-
 pkg/controller/integration/initialize.go           |   5 +-
 .../integration/integration_controller.go          |  27 +++-
 pkg/controller/integration/util.go                 |  20 ++-
 .../integrationkit/integrationkit_controller.go    |   6 +-
 pkg/platform/platform.go                           |  20 ++-
 pkg/trait/deployment.go                            |  27 +++-
 pkg/trait/ingress.go                               |  71 ++++++----
 pkg/trait/knative_service.go                       |  59 +++++++--
 pkg/trait/route.go                                 |  77 ++++++-----
 pkg/trait/route_test.go                            |   2 +-
 pkg/trait/service.go                               |  49 +++++--
 pkg/trait/trait_types.go                           |   7 +-
 pkg/util/indentedwriter/writer.go                  |  16 ++-
 pkg/util/kubernetes/collection.go                  |  37 ++++++
 pkg/util/kubernetes/resolver.go                    |  17 ++-
 pkg/util/kubernetes/util.go                        |  32 +++--
 30 files changed, 1069 insertions(+), 206 deletions(-)

diff --git a/pkg/apis/camel/v1alpha1/build_types.go 
b/pkg/apis/camel/v1alpha1/build_types.go
index 8f8e90a..43b4660 100644
--- a/pkg/apis/camel/v1alpha1/build_types.go
+++ b/pkg/apis/camel/v1alpha1/build_types.go
@@ -18,6 +18,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -42,16 +43,17 @@ type BuildSpec struct {
 
 // BuildStatus defines the observed state of Build
 type BuildStatus struct {
-       // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
-       // Important: Run "operator-sdk generate k8s" to regenerate code after 
modifying this file
-       Phase       BuildPhase  `json:"phase,omitempty"`
-       Image       string      `json:"image,omitempty"`
-       BaseImage   string      `json:"baseImage,omitempty"`
-       PublicImage string      `json:"publicImage,omitempty"`
-       Artifacts   []Artifact  `json:"artifacts,omitempty"`
-       Error       string      `json:"error,omitempty"`
-       Failure     *Failure    `json:"failure,omitempty"`
-       StartedAt   metav1.Time `json:"startedAt,omitempty"`
+       Phase       BuildPhase       `json:"phase,omitempty"`
+       Image       string           `json:"image,omitempty"`
+       BaseImage   string           `json:"baseImage,omitempty"`
+       PublicImage string           `json:"publicImage,omitempty"`
+       Artifacts   []Artifact       `json:"artifacts,omitempty"`
+       Error       string           `json:"error,omitempty"`
+       Failure     *Failure         `json:"failure,omitempty"`
+       StartedAt   metav1.Time      `json:"startedAt,omitempty"`
+       Platform    string           `json:"platform,omitempty"`
+       Conditions  []BuildCondition `json:"conditions,omitempty"`
+
        // Change to Duration / ISO 8601 when CRD uses OpenAPI spec v3
        // https://github.com/OAI/OpenAPI-Specification/issues/845
        Duration string `json:"duration,omitempty"`
@@ -60,6 +62,9 @@ type BuildStatus struct {
 // BuildPhase --
 type BuildPhase string
 
+// BuildConditionType --
+type BuildConditionType string
+
 const (
        // BuildKind --
        BuildKind string = "Build"
@@ -84,6 +89,11 @@ const (
        BuildPhaseInterrupted = "Interrupted"
        // BuildPhaseError --
        BuildPhaseError BuildPhase = "Error"
+
+       // BuildConditionPlatformAvailable --
+       BuildConditionPlatformAvailable BuildConditionType = 
"IntegrationPlatformAvailable"
+       // BuildConditionPlatformAvailableReason --
+       BuildConditionPlatformAvailableReason string = 
"IntegrationPlatformAvailable"
 )
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -107,6 +117,22 @@ type BuildList struct {
        Items           []Build `json:"items"`
 }
 
+// Condition describes the state of a resource at a certain point.
+type BuildCondition struct {
+       // Type of integration condition.
+       Type BuildConditionType `json:"type"`
+       // Status of the condition, one of True, False, Unknown.
+       Status corev1.ConditionStatus `json:"status"`
+       // The last time this condition was updated.
+       LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
+       // Last time the condition transitioned from one status to another.
+       LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+       // The reason for the condition's last transition.
+       Reason string `json:"reason,omitempty"`
+       // A human readable message indicating details about the transition.
+       Message string `json:"message,omitempty"`
+}
+
 func init() {
        SchemeBuilder.Register(&Build{}, &BuildList{})
 }
diff --git a/pkg/apis/camel/v1alpha1/build_types_support.go 
b/pkg/apis/camel/v1alpha1/build_types_support.go
index a49a4aa..8aae4ad 100644
--- a/pkg/apis/camel/v1alpha1/build_types_support.go
+++ b/pkg/apis/camel/v1alpha1/build_types_support.go
@@ -17,7 +17,10 @@ limitations under the License.
 
 package v1alpha1
 
-import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+import (
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 // NewBuild --
 func NewBuild(namespace string, name string) Build {
@@ -42,3 +45,90 @@ func NewBuildList() BuildList {
                },
        }
 }
+
+// SetIntegrationPlatform --
+func (in *Build) SetIntegrationPlatform(platform *IntegrationPlatform) {
+       cs := corev1.ConditionTrue
+
+       if platform.Status.Phase != IntegrationPlatformPhaseReady {
+               cs = corev1.ConditionFalse
+       }
+
+       in.Status.SetCondition(BuildConditionPlatformAvailable, cs, 
BuildConditionPlatformAvailableReason, platform.Name)
+       in.Status.Platform = platform.Name
+}
+
+// GetCondition returns the condition with the provided type.
+func (in *BuildStatus) GetCondition(condType BuildConditionType) 
*BuildCondition {
+       for i := range in.Conditions {
+               c := in.Conditions[i]
+               if c.Type == condType {
+                       return &c
+               }
+       }
+       return nil
+}
+
+// SetCondition --
+func (in *BuildStatus) SetCondition(condType BuildConditionType, status 
corev1.ConditionStatus, reason string, message string) {
+       in.SetConditions(BuildCondition{
+               Type:               condType,
+               Status:             status,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            message,
+       })
+}
+
+// SetErrorCondition --
+func (in *BuildStatus) SetErrorCondition(condType BuildConditionType, reason 
string, err error) {
+       in.SetConditions(BuildCondition{
+               Type:               condType,
+               Status:             corev1.ConditionFalse,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            err.Error(),
+       })
+}
+
+// SetConditions updates the resource to include the provided conditions.
+//
+// If a condition that we are about to add already exists and has the same 
status and
+// reason then we are not going to update.
+func (in *BuildStatus) SetConditions(conditions ...BuildCondition) {
+       for _, condition := range conditions {
+               if condition.LastUpdateTime.IsZero() {
+                       condition.LastUpdateTime = metav1.Now()
+               }
+               if condition.LastTransitionTime.IsZero() {
+                       condition.LastTransitionTime = metav1.Now()
+               }
+
+               currentCond := in.GetCondition(condition.Type)
+
+               if currentCond != nil && currentCond.Status == condition.Status 
&& currentCond.Reason == condition.Reason {
+                       return
+               }
+               // Do not update lastTransitionTime if the status of the 
condition doesn't change.
+               if currentCond != nil && currentCond.Status == condition.Status 
{
+                       condition.LastTransitionTime = 
currentCond.LastTransitionTime
+               }
+
+               in.RemoveCondition(condition.Type)
+               in.Conditions = append(in.Conditions, condition)
+       }
+}
+
+// RemoveCondition removes the resource condition with the provided type.
+func (in *BuildStatus) RemoveCondition(condType BuildConditionType) {
+       newConditions := in.Conditions[:0]
+       for _, c := range in.Conditions {
+               if c.Type != condType {
+                       newConditions = append(newConditions, c)
+               }
+       }
+
+       in.Conditions = newConditions
+}
diff --git a/pkg/apis/camel/v1alpha1/common_types.go 
b/pkg/apis/camel/v1alpha1/common_types.go
index 1c57c62..cf74f69 100644
--- a/pkg/apis/camel/v1alpha1/common_types.go
+++ b/pkg/apis/camel/v1alpha1/common_types.go
@@ -59,6 +59,11 @@ type Configurable interface {
        Configurations() []ConfigurationSpec
 }
 
+// PlatformInjectable --
+type PlatformInjectable interface {
+       SetIntegrationPlatform(platform *IntegrationPlatform)
+}
+
 // MavenSpec --
 type MavenSpec struct {
        Settings ValueSource     `json:"settings,omitempty"`
@@ -72,3 +77,8 @@ type ValueSource struct {
        // Selects a key of a secret.
        SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty" `
 }
+
+const (
+       // ServiceTypeUser --
+       ServiceTypeUser = "user"
+)
diff --git a/pkg/apis/camel/v1alpha1/integration_types.go 
b/pkg/apis/camel/v1alpha1/integration_types.go
index f94320d..b6435af 100644
--- a/pkg/apis/camel/v1alpha1/integration_types.go
+++ b/pkg/apis/camel/v1alpha1/integration_types.go
@@ -18,6 +18,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -39,17 +40,19 @@ type IntegrationSpec struct {
 
 // IntegrationStatus defines the observed state of Integration
 type IntegrationStatus struct {
-       Phase            IntegrationPhase    `json:"phase,omitempty"`
-       Digest           string              `json:"digest,omitempty"`
-       Image            string              `json:"image,omitempty"`
-       Dependencies     []string            `json:"dependencies,omitempty"`
-       Kit              string              `json:"kit,omitempty"`
-       GeneratedSources []SourceSpec        `json:"generatedSources,omitempty"`
-       Failure          *Failure            `json:"failure,omitempty"`
-       CamelVersion     string              `json:"camelVersion,omitempty"`
-       RuntimeVersion   string              `json:"runtimeVersion,omitempty"`
-       Configuration    []ConfigurationSpec `json:"configuration,omitempty"`
-       Version          string              `json:"version,omitempty"`
+       Phase            IntegrationPhase       `json:"phase,omitempty"`
+       Digest           string                 `json:"digest,omitempty"`
+       Image            string                 `json:"image,omitempty"`
+       Dependencies     []string               `json:"dependencies,omitempty"`
+       Kit              string                 `json:"kit,omitempty"`
+       Platform         string                 `json:"platform,omitempty"`
+       GeneratedSources []SourceSpec           
`json:"generatedSources,omitempty"`
+       Failure          *Failure               `json:"failure,omitempty"`
+       CamelVersion     string                 `json:"camelVersion,omitempty"`
+       RuntimeVersion   string                 
`json:"runtimeVersion,omitempty"`
+       Configuration    []ConfigurationSpec    `json:"configuration,omitempty"`
+       Conditions       []IntegrationCondition `json:"conditions,omitempty"`
+       Version          string                 `json:"version,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -139,6 +142,9 @@ var Languages = []Language{
 // IntegrationPhase --
 type IntegrationPhase string
 
+// IntegrationConditionType --
+type IntegrationConditionType string
+
 const (
        // IntegrationKind --
        IntegrationKind string = "Integration"
@@ -161,8 +167,62 @@ const (
        IntegrationPhaseError IntegrationPhase = "Error"
        // IntegrationPhaseDeleting --
        IntegrationPhaseDeleting IntegrationPhase = "Deleting"
+
+       // IntegrationConditionKitAvailable --
+       IntegrationConditionKitAvailable IntegrationConditionType = 
"IntegrationKitAvailable"
+       // IntegrationConditionPlatformAvailable --
+       IntegrationConditionPlatformAvailable IntegrationConditionType = 
"IntegrationPlatformAvailable"
+       // IntegrationConditionDeploymentAvailable --
+       IntegrationConditionDeploymentAvailable IntegrationConditionType = 
"DeploymentAvailable"
+       // IntegrationConditionServiceAvailable --
+       IntegrationConditionServiceAvailable IntegrationConditionType = 
"ServiceAvailable"
+       // IntegrationConditionKnativeServiceAvailable --
+       IntegrationConditionKnativeServiceAvailable IntegrationConditionType = 
"KnativeServiceAvailable"
+       // IntegrationConditionExposureAvailable --
+       IntegrationConditionExposureAvailable IntegrationConditionType = 
"ExposureAvailable"
+
+       // IntegrationConditionKitAvailableReason --
+       IntegrationConditionKitAvailableReason string = 
"IntegrationKitAvailable"
+       // IntegrationConditionPlatformAvailableReason --
+       IntegrationConditionPlatformAvailableReason string = 
"IntegrationPlatformAvailable"
+       // IntegrationConditionDeploymentAvailableReason --
+       IntegrationConditionDeploymentAvailableReason string = 
"DeploymentAvailable"
+       // IntegrationConditionDeploymentNotAvailableReason --
+       IntegrationConditionDeploymentNotAvailableReason string = 
"DeploymentNotAvailable"
+       // IntegrationConditionServiceAvailableReason --
+       IntegrationConditionServiceAvailableReason string = "ServiceAvailable"
+       // IntegrationConditionServiceNotAvailableReason --
+       IntegrationConditionServiceNotAvailableReason string = 
"ServiceNotAvailable"
+       // IntegrationConditionRouteAvailableReason --
+       IntegrationConditionRouteAvailableReason string = "RouteAvailable"
+       // IntegrationConditionRouteNotAvailableReason --
+       IntegrationConditionRouteNotAvailableReason string = "RouteNotAvailable"
+       // IntegrationConditionIngressAvailableReason --
+       IntegrationConditionIngressAvailableReason string = "IngressAvailable"
+       // IntegrationConditionIngressNotAvailableReason --
+       IntegrationConditionIngressNotAvailableReason string = 
"IngressNotAvailable"
+       // IntegrationConditionKnativeServiceAvailableReason --
+       IntegrationConditionKnativeServiceAvailableReason string = 
"KnativeServiceAvailable"
+       // IntegrationConditionKnativeServiceNotAvailableReason --
+       IntegrationConditionKnativeServiceNotAvailableReason string = 
"KnativeServiceNotAvailable"
 )
 
+// IntegrationCondition describes the state of a resource at a certain point.
+type IntegrationCondition struct {
+       // Type of integration condition.
+       Type IntegrationConditionType `json:"type"`
+       // Status of the condition, one of True, False, Unknown.
+       Status corev1.ConditionStatus `json:"status"`
+       // The last time this condition was updated.
+       LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
+       // Last time the condition transitioned from one status to another.
+       LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+       // The reason for the condition's last transition.
+       Reason string `json:"reason,omitempty"`
+       // A human readable message indicating details about the transition.
+       Message string `json:"message,omitempty"`
+}
+
 func init() {
        SchemeBuilder.Register(&Integration{}, &IntegrationList{})
 }
diff --git a/pkg/apis/camel/v1alpha1/integration_types_support.go 
b/pkg/apis/camel/v1alpha1/integration_types_support.go
index e7e089c..1f72a5e 100644
--- a/pkg/apis/camel/v1alpha1/integration_types_support.go
+++ b/pkg/apis/camel/v1alpha1/integration_types_support.go
@@ -20,6 +20,7 @@ package v1alpha1
 import (
        "strings"
 
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -57,61 +58,61 @@ func (in *Integration) Sources() []SourceSpec {
 }
 
 // AddSource --
-func (is *IntegrationSpec) AddSource(name string, content string, language 
Language) {
-       is.Sources = append(is.Sources, NewSourceSpec(name, content, language))
+func (in *IntegrationSpec) AddSource(name string, content string, language 
Language) {
+       in.Sources = append(in.Sources, NewSourceSpec(name, content, language))
 }
 
 // AddSources --
-func (is *IntegrationSpec) AddSources(sources ...SourceSpec) {
-       is.Sources = append(is.Sources, sources...)
+func (in *IntegrationSpec) AddSources(sources ...SourceSpec) {
+       in.Sources = append(in.Sources, sources...)
 }
 
 // AddResources --
-func (is *IntegrationSpec) AddResources(resources ...ResourceSpec) {
-       is.Resources = append(is.Resources, resources...)
+func (in *IntegrationSpec) AddResources(resources ...ResourceSpec) {
+       in.Resources = append(in.Resources, resources...)
 }
 
 // AddConfiguration --
-func (is *IntegrationSpec) AddConfiguration(confType string, confValue string) 
{
-       is.Configuration = append(is.Configuration, ConfigurationSpec{
+func (in *IntegrationSpec) AddConfiguration(confType string, confValue string) 
{
+       in.Configuration = append(in.Configuration, ConfigurationSpec{
                Type:  confType,
                Value: confValue,
        })
 }
 
 // AddDependency --
-func (is *IntegrationSpec) AddDependency(dependency string) {
-       if is.Dependencies == nil {
-               is.Dependencies = make([]string, 0)
+func (in *IntegrationSpec) AddDependency(dependency string) {
+       if in.Dependencies == nil {
+               in.Dependencies = make([]string, 0)
        }
        newDep := dependency
        if strings.HasPrefix(newDep, "camel-") {
                newDep = "camel:" + strings.TrimPrefix(dependency, "camel-")
        }
-       for _, d := range is.Dependencies {
+       for _, d := range in.Dependencies {
                if d == newDep {
                        return
                }
        }
-       is.Dependencies = append(is.Dependencies, newDep)
+       in.Dependencies = append(in.Dependencies, newDep)
 }
 
 // Configurations --
-func (is *IntegrationSpec) Configurations() []ConfigurationSpec {
-       if is == nil {
+func (in *IntegrationSpec) Configurations() []ConfigurationSpec {
+       if in == nil {
                return []ConfigurationSpec{}
        }
 
-       return is.Configuration
+       return in.Configuration
 }
 
 // Configurations --
-func (is *IntegrationStatus) Configurations() []ConfigurationSpec {
-       if is == nil {
+func (in *IntegrationStatus) Configurations() []ConfigurationSpec {
+       if in == nil {
                return []ConfigurationSpec{}
        }
 
-       return is.Configuration
+       return in.Configuration
 }
 
 // Configurations --
@@ -150,14 +151,114 @@ func NewResourceSpec(name string, content string, 
destination string, resourceTy
 }
 
 // InferLanguage returns the language of the source or discovers it from file 
extension if not set
-func (s SourceSpec) InferLanguage() Language {
-       if s.Language != "" {
-               return s.Language
+func (in *SourceSpec) InferLanguage() Language {
+       if in.Language != "" {
+               return in.Language
        }
        for _, l := range Languages {
-               if strings.HasSuffix(s.Name, "."+string(l)) {
+               if strings.HasSuffix(in.Name, "."+string(l)) {
                        return l
                }
        }
        return ""
 }
+
+// SetIntegrationPlatform --
+func (in *Integration) SetIntegrationPlatform(platform *IntegrationPlatform) {
+       cs := corev1.ConditionTrue
+
+       if platform.Status.Phase != IntegrationPlatformPhaseReady {
+               cs = corev1.ConditionFalse
+       }
+
+       in.Status.SetCondition(IntegrationConditionPlatformAvailable, cs, 
IntegrationConditionPlatformAvailableReason, platform.Name)
+       in.Status.Platform = platform.Name
+}
+
+// SetIntegrationKit --
+func (in *Integration) SetIntegrationKit(kit *IntegrationKit) {
+       cs := corev1.ConditionTrue
+
+       if kit.Status.Phase != IntegrationKitPhaseReady {
+               cs = corev1.ConditionFalse
+       }
+
+       in.Status.SetCondition(IntegrationConditionKitAvailable, cs, 
IntegrationConditionKitAvailableReason, kit.Name)
+       in.Status.Kit = kit.Name
+       in.Status.Image = kit.ImageForIntegration()
+}
+
+// GetCondition returns the condition with the provided type.
+func (in *IntegrationStatus) GetCondition(condType IntegrationConditionType) 
*IntegrationCondition {
+       for i := range in.Conditions {
+               c := in.Conditions[i]
+               if c.Type == condType {
+                       return &c
+               }
+       }
+       return nil
+}
+
+// SetCondition --
+func (in *IntegrationStatus) SetCondition(condType IntegrationConditionType, 
status corev1.ConditionStatus, reason string, message string) {
+       in.SetConditions(IntegrationCondition{
+               Type:               condType,
+               Status:             status,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            message,
+       })
+}
+
+// SetErrorCondition --
+func (in *IntegrationStatus) SetErrorCondition(condType 
IntegrationConditionType, reason string, err error) {
+       in.SetConditions(IntegrationCondition{
+               Type:               condType,
+               Status:             corev1.ConditionFalse,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            err.Error(),
+       })
+}
+
+// SetConditions updates the resource to include the provided conditions.
+//
+// If a condition that we are about to add already exists and has the same 
status and
+// reason then we are not going to update.
+func (in *IntegrationStatus) SetConditions(conditions ...IntegrationCondition) 
{
+       for _, condition := range conditions {
+               if condition.LastUpdateTime.IsZero() {
+                       condition.LastUpdateTime = metav1.Now()
+               }
+               if condition.LastTransitionTime.IsZero() {
+                       condition.LastTransitionTime = metav1.Now()
+               }
+
+               currentCond := in.GetCondition(condition.Type)
+
+               if currentCond != nil && currentCond.Status == condition.Status 
&& currentCond.Reason == condition.Reason {
+                       return
+               }
+               // Do not update lastTransitionTime if the status of the 
condition doesn't change.
+               if currentCond != nil && currentCond.Status == condition.Status 
{
+                       condition.LastTransitionTime = 
currentCond.LastTransitionTime
+               }
+
+               in.RemoveCondition(condition.Type)
+               in.Conditions = append(in.Conditions, condition)
+       }
+}
+
+// RemoveCondition removes the resource condition with the provided type.
+func (in *IntegrationStatus) RemoveCondition(condType 
IntegrationConditionType) {
+       newConditions := in.Conditions[:0]
+       for _, c := range in.Conditions {
+               if c.Type != condType {
+                       newConditions = append(newConditions, c)
+               }
+       }
+
+       in.Conditions = newConditions
+}
diff --git a/pkg/apis/camel/v1alpha1/integrationkit_types.go 
b/pkg/apis/camel/v1alpha1/integrationkit_types.go
index f4c584f..0228859 100644
--- a/pkg/apis/camel/v1alpha1/integrationkit_types.go
+++ b/pkg/apis/camel/v1alpha1/integrationkit_types.go
@@ -17,6 +17,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -34,16 +35,18 @@ type IntegrationKitSpec struct {
 
 // IntegrationKitStatus defines the observed state of IntegrationKit
 type IntegrationKitStatus struct {
-       Phase          IntegrationKitPhase `json:"phase,omitempty"`
-       BaseImage      string              `json:"baseImage,omitempty"`
-       Image          string              `json:"image,omitempty"`
-       PublicImage    string              `json:"publicImage,omitempty"`
-       Digest         string              `json:"digest,omitempty"`
-       Artifacts      []Artifact          `json:"artifacts,omitempty"`
-       Failure        *Failure            `json:"failure,omitempty"`
-       CamelVersion   string              `json:"camelVersion,omitempty"`
-       RuntimeVersion string              `json:"runtimeVersion,omitempty"`
-       Version        string              `json:"version,omitempty"`
+       Phase          IntegrationKitPhase       `json:"phase,omitempty"`
+       BaseImage      string                    `json:"baseImage,omitempty"`
+       Image          string                    `json:"image,omitempty"`
+       PublicImage    string                    `json:"publicImage,omitempty"`
+       Digest         string                    `json:"digest,omitempty"`
+       Artifacts      []Artifact                `json:"artifacts,omitempty"`
+       Failure        *Failure                  `json:"failure,omitempty"`
+       CamelVersion   string                    `json:"camelVersion,omitempty"`
+       RuntimeVersion string                    
`json:"runtimeVersion,omitempty"`
+       Platform       string                    `json:"platform,omitempty"`
+       Conditions     []IntegrationKitCondition `json:"conditions,omitempty"`
+       Version        string                    `json:"version,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -70,6 +73,9 @@ type IntegrationKitList struct {
 // IntegrationKitPhase --
 type IntegrationKitPhase string
 
+// IntegrationKitConditionType --
+type IntegrationKitConditionType string
+
 const (
        // IntegrationKindKind --
        IntegrationKindKind string = "IntegrationKit"
@@ -97,8 +103,29 @@ const (
        IntegrationKitPhaseReady IntegrationKitPhase = "Ready"
        // IntegrationKitPhaseError --
        IntegrationKitPhaseError IntegrationKitPhase = "Error"
+
+       // IntegrationKitConditionPlatformAvailable --
+       IntegrationKitConditionPlatformAvailable IntegrationKitConditionType = 
"IntegrationPlatformAvailable"
+       // IntegrationKitConditionPlatformAvailableReason --
+       IntegrationKitConditionPlatformAvailableReason string = 
"IntegrationPlatformAvailable"
 )
 
+// Condition describes the state of a resource at a certain point.
+type IntegrationKitCondition struct {
+       // Type of integration condition.
+       Type IntegrationKitConditionType `json:"type"`
+       // Status of the condition, one of True, False, Unknown.
+       Status corev1.ConditionStatus `json:"status"`
+       // The last time this condition was updated.
+       LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
+       // Last time the condition transitioned from one status to another.
+       LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+       // The reason for the condition's last transition.
+       Reason string `json:"reason,omitempty"`
+       // A human readable message indicating details about the transition.
+       Message string `json:"message,omitempty"`
+}
+
 func init() {
        SchemeBuilder.Register(&IntegrationKit{}, &IntegrationKitList{})
 }
diff --git a/pkg/apis/camel/v1alpha1/integrationkit_types_support.go 
b/pkg/apis/camel/v1alpha1/integrationkit_types_support.go
index 46ce1fe..558cd96 100644
--- a/pkg/apis/camel/v1alpha1/integrationkit_types_support.go
+++ b/pkg/apis/camel/v1alpha1/integrationkit_types_support.go
@@ -17,7 +17,10 @@ limitations under the License.
 
 package v1alpha1
 
-import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+import (
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
 
 // NewIntegrationKit --
 func NewIntegrationKit(namespace string, name string) IntegrationKit {
@@ -68,3 +71,95 @@ func (in *IntegrationKit) Configurations() 
[]ConfigurationSpec {
 
        return in.Spec.Configuration
 }
+
+// SetIntegrationPlatform --
+func (in *IntegrationKit) SetIntegrationPlatform(platform 
*IntegrationPlatform) {
+       cs := corev1.ConditionTrue
+
+       if platform.Status.Phase != IntegrationPlatformPhaseReady {
+               cs = corev1.ConditionFalse
+       }
+
+       var message string
+       if platform.Name != "" {
+               message = "IntegrationPlatform (" + platform.Name + ")"
+       }
+
+       in.Status.SetCondition(IntegrationKitConditionPlatformAvailable, cs, 
IntegrationKitConditionPlatformAvailableReason, message)
+       in.Status.Platform = platform.Name
+}
+
+// GetCondition returns the condition with the provided type.
+func (in *IntegrationKitStatus) GetCondition(condType 
IntegrationKitConditionType) *IntegrationKitCondition {
+       for i := range in.Conditions {
+               c := in.Conditions[i]
+               if c.Type == condType {
+                       return &c
+               }
+       }
+       return nil
+}
+
+// SetCondition --
+func (in *IntegrationKitStatus) SetCondition(condType 
IntegrationKitConditionType, status corev1.ConditionStatus, reason string, 
message string) {
+       in.SetConditions(IntegrationKitCondition{
+               Type:               condType,
+               Status:             status,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            message,
+       })
+}
+
+// SetErrorCondition --
+func (in *IntegrationKitStatus) SetErrorCondition(condType 
IntegrationKitConditionType, reason string, err error) {
+       in.SetConditions(IntegrationKitCondition{
+               Type:               condType,
+               Status:             corev1.ConditionFalse,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            err.Error(),
+       })
+}
+
+// SetConditions updates the resource to include the provided conditions.
+//
+// If a condition that we are about to add already exists and has the same 
status and
+// reason then we are not going to update.
+func (in *IntegrationKitStatus) SetConditions(conditions 
...IntegrationKitCondition) {
+       for _, condition := range conditions {
+               if condition.LastUpdateTime.IsZero() {
+                       condition.LastUpdateTime = metav1.Now()
+               }
+               if condition.LastTransitionTime.IsZero() {
+                       condition.LastTransitionTime = metav1.Now()
+               }
+
+               currentCond := in.GetCondition(condition.Type)
+
+               if currentCond != nil && currentCond.Status == condition.Status 
&& currentCond.Reason == condition.Reason {
+                       return
+               }
+               // Do not update lastTransitionTime if the status of the 
condition doesn't change.
+               if currentCond != nil && currentCond.Status == condition.Status 
{
+                       condition.LastTransitionTime = 
currentCond.LastTransitionTime
+               }
+
+               in.RemoveCondition(condition.Type)
+               in.Conditions = append(in.Conditions, condition)
+       }
+}
+
+// RemoveCondition removes the resource condition with the provided type.
+func (in *IntegrationKitStatus) RemoveCondition(condType 
IntegrationKitConditionType) {
+       newConditions := in.Conditions[:0]
+       for _, c := range in.Conditions {
+               if c.Type != condType {
+                       newConditions = append(newConditions, c)
+               }
+       }
+
+       in.Conditions = newConditions
+}
diff --git a/pkg/apis/camel/v1alpha1/integrationplatform_types.go 
b/pkg/apis/camel/v1alpha1/integrationplatform_types.go
index e997d2c..9a1e047 100644
--- a/pkg/apis/camel/v1alpha1/integrationplatform_types.go
+++ b/pkg/apis/camel/v1alpha1/integrationplatform_types.go
@@ -18,6 +18,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -40,8 +41,9 @@ type IntegrationPlatformResourcesSpec struct {
 
 // IntegrationPlatformStatus defines the observed state of IntegrationPlatform
 type IntegrationPlatformStatus struct {
-       Phase   IntegrationPlatformPhase `json:"phase,omitempty"`
-       Version string                   `json:"version,omitempty"`
+       Phase      IntegrationPlatformPhase       `json:"phase,omitempty"`
+       Conditions []IntegrationPlatformCondition `json:"conditions,omitempty"`
+       Version    string                         `json:"version,omitempty"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -137,6 +139,9 @@ const (
 // IntegrationPlatformPhase --
 type IntegrationPlatformPhase string
 
+// IntegrationPlatformConditionType --
+type IntegrationPlatformConditionType string
+
 const (
        // IntegrationPlatformKind --
        IntegrationPlatformKind string = "IntegrationPlatform"
@@ -153,6 +158,22 @@ const (
        IntegrationPlatformPhaseDuplicate IntegrationPlatformPhase = "Duplicate"
 )
 
+// Condition describes the state of a resource at a certain point.
+type IntegrationPlatformCondition struct {
+       // Type of integration condition.
+       Type IntegrationPlatformConditionType `json:"type"`
+       // Status of the condition, one of True, False, Unknown.
+       Status corev1.ConditionStatus `json:"status"`
+       // The last time this condition was updated.
+       LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
+       // Last time the condition transitioned from one status to another.
+       LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
+       // The reason for the condition's last transition.
+       Reason string `json:"reason,omitempty"`
+       // A human readable message indicating details about the transition.
+       Message string `json:"message,omitempty"`
+}
+
 func init() {
        SchemeBuilder.Register(&IntegrationPlatform{}, 
&IntegrationPlatformList{})
 }
diff --git a/pkg/apis/camel/v1alpha1/integrationplatform_types_support.go 
b/pkg/apis/camel/v1alpha1/integrationplatform_types_support.go
index a583244..ff35966 100644
--- a/pkg/apis/camel/v1alpha1/integrationplatform_types_support.go
+++ b/pkg/apis/camel/v1alpha1/integrationplatform_types_support.go
@@ -20,6 +20,7 @@ package v1alpha1
 import (
        "strings"
 
+       corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -74,3 +75,79 @@ func (in *IntegrationPlatform) Configurations() 
[]ConfigurationSpec {
 
        return in.Spec.Configuration
 }
+
+// GetCondition returns the condition with the provided type.
+func (in *IntegrationPlatformStatus) GetCondition(condType 
IntegrationPlatformConditionType) *IntegrationPlatformCondition {
+       for i := range in.Conditions {
+               c := in.Conditions[i]
+               if c.Type == condType {
+                       return &c
+               }
+
+       }
+       return nil
+}
+
+// SetCondition --
+func (in *IntegrationPlatformStatus) SetCondition(condType 
IntegrationPlatformConditionType, status corev1.ConditionStatus, reason string, 
message string) {
+       in.SetConditions(IntegrationPlatformCondition{
+               Type:               condType,
+               Status:             status,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            message,
+       })
+}
+
+// SetErrorCondition --
+func (in *IntegrationPlatformStatus) SetErrorCondition(condType 
IntegrationPlatformConditionType, reason string, err error) {
+       in.SetConditions(IntegrationPlatformCondition{
+               Type:               condType,
+               Status:             corev1.ConditionFalse,
+               LastUpdateTime:     metav1.Now(),
+               LastTransitionTime: metav1.Now(),
+               Reason:             reason,
+               Message:            err.Error(),
+       })
+}
+
+// SetConditions updates the resource to include the provided conditions.
+//
+// If a condition that we are about to add already exists and has the same 
status and
+// reason then we are not going to update.
+func (in *IntegrationPlatformStatus) SetConditions(conditions 
...IntegrationPlatformCondition) {
+       for _, condition := range conditions {
+               if condition.LastUpdateTime.IsZero() {
+                       condition.LastUpdateTime = metav1.Now()
+               }
+               if condition.LastTransitionTime.IsZero() {
+                       condition.LastTransitionTime = metav1.Now()
+               }
+
+               currentCond := in.GetCondition(condition.Type)
+
+               if currentCond != nil && currentCond.Status == condition.Status 
&& currentCond.Reason == condition.Reason {
+                       return
+               }
+               // Do not update lastTransitionTime if the status of the 
condition doesn't change.
+               if currentCond != nil && currentCond.Status == condition.Status 
{
+                       condition.LastTransitionTime = 
currentCond.LastTransitionTime
+               }
+
+               in.RemoveCondition(condition.Type)
+               in.Conditions = append(in.Conditions, condition)
+       }
+}
+
+// RemoveCondition removes the resource condition with the provided type.
+func (in *IntegrationPlatformStatus) RemoveCondition(condType 
IntegrationPlatformConditionType) {
+       newConditions := in.Conditions[:0]
+       for _, c := range in.Conditions {
+               if c.Type != condType {
+                       newConditions = append(newConditions, c)
+               }
+       }
+
+       in.Conditions = newConditions
+}
diff --git a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go 
b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
index 43e14e8..5f31fa6 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -54,6 +54,24 @@ func (in *Build) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *BuildCondition) DeepCopyInto(out *BuildCondition) {
+       *out = *in
+       in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
+       in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new BuildCondition.
+func (in *BuildCondition) DeepCopy() *BuildCondition {
+       if in == nil {
+               return nil
+       }
+       out := new(BuildCondition)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *BuildList) DeepCopyInto(out *BuildList) {
        *out = *in
        out.TypeMeta = in.TypeMeta
@@ -138,6 +156,13 @@ func (in *BuildStatus) DeepCopyInto(out *BuildStatus) {
                (*in).DeepCopyInto(*out)
        }
        in.StartedAt.DeepCopyInto(&out.StartedAt)
+       if in.Conditions != nil {
+               in, out := &in.Conditions, &out.Conditions
+               *out = make([]BuildCondition, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
        return
 }
 
@@ -439,6 +464,24 @@ func (in *Integration) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *IntegrationCondition) DeepCopyInto(out *IntegrationCondition) {
+       *out = *in
+       in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
+       in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new IntegrationCondition.
+func (in *IntegrationCondition) DeepCopy() *IntegrationCondition {
+       if in == nil {
+               return nil
+       }
+       out := new(IntegrationCondition)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *IntegrationKit) DeepCopyInto(out *IntegrationKit) {
        *out = *in
        out.TypeMeta = in.TypeMeta
@@ -467,6 +510,24 @@ func (in *IntegrationKit) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *IntegrationKitCondition) DeepCopyInto(out *IntegrationKitCondition) {
+       *out = *in
+       in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
+       in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new IntegrationKitCondition.
+func (in *IntegrationKitCondition) DeepCopy() *IntegrationKitCondition {
+       if in == nil {
+               return nil
+       }
+       out := new(IntegrationKitCondition)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *IntegrationKitList) DeepCopyInto(out *IntegrationKitList) {
        *out = *in
        out.TypeMeta = in.TypeMeta
@@ -550,6 +611,13 @@ func (in *IntegrationKitStatus) DeepCopyInto(out 
*IntegrationKitStatus) {
                *out = new(Failure)
                (*in).DeepCopyInto(*out)
        }
+       if in.Conditions != nil {
+               in, out := &in.Conditions, &out.Conditions
+               *out = make([]IntegrationKitCondition, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
        return
 }
 
@@ -602,7 +670,7 @@ func (in *IntegrationPlatform) DeepCopyInto(out 
*IntegrationPlatform) {
        out.TypeMeta = in.TypeMeta
        in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
        in.Spec.DeepCopyInto(&out.Spec)
-       out.Status = in.Status
+       in.Status.DeepCopyInto(&out.Status)
        return
 }
 
@@ -651,6 +719,24 @@ func (in *IntegrationPlatformBuildSpec) DeepCopy() 
*IntegrationPlatformBuildSpec
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *IntegrationPlatformCondition) DeepCopyInto(out 
*IntegrationPlatformCondition) {
+       *out = *in
+       in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)
+       in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new IntegrationPlatformCondition.
+func (in *IntegrationPlatformCondition) DeepCopy() 
*IntegrationPlatformCondition {
+       if in == nil {
+               return nil
+       }
+       out := new(IntegrationPlatformCondition)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *IntegrationPlatformList) DeepCopyInto(out *IntegrationPlatformList) {
        *out = *in
        out.TypeMeta = in.TypeMeta
@@ -753,6 +839,13 @@ func (in *IntegrationPlatformSpec) DeepCopy() 
*IntegrationPlatformSpec {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
 func (in *IntegrationPlatformStatus) DeepCopyInto(out 
*IntegrationPlatformStatus) {
        *out = *in
+       if in.Conditions != nil {
+               in, out := &in.Conditions, &out.Conditions
+               *out = make([]IntegrationPlatformCondition, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
        return
 }
 
@@ -842,6 +935,13 @@ func (in *IntegrationStatus) DeepCopyInto(out 
*IntegrationStatus) {
                *out = make([]ConfigurationSpec, len(*in))
                copy(*out, *in)
        }
+       if in.Conditions != nil {
+               in, out := &in.Conditions, &out.Conditions
+               *out = make([]IntegrationCondition, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
        return
 }
 
diff --git a/pkg/cmd/describe_integration.go b/pkg/cmd/describe_integration.go
index 5d70cfd..117868f 100644
--- a/pkg/cmd/describe_integration.go
+++ b/pkg/cmd/describe_integration.go
@@ -22,17 +22,18 @@ import (
        "io"
        "strings"
 
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/util/indentedwriter"
 
-       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/spf13/cobra"
+
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 func newDescribeIntegrationCmd(rootCmdOptions *RootCmdOptions) *cobra.Command {
 
        impl := &describeIntegrationCommand{
-               rootCmdOptions,
+               RootCmdOptions: rootCmdOptions,
        }
 
        cmd := cobra.Command{
@@ -52,11 +53,14 @@ func newDescribeIntegrationCmd(rootCmdOptions 
*RootCmdOptions) *cobra.Command {
                },
        }
 
+       cmd.Flags().BoolVar(&impl.showSourceContent, "show-source-content", 
false, "Print source content")
+
        return &cmd
 }
 
 type describeIntegrationCommand struct {
        *RootCmdOptions
+       showSourceContent bool
 }
 
 func (command *describeIntegrationCommand) validate(args []string) error {
@@ -133,10 +137,42 @@ func (command *describeIntegrationCommand) 
describeIntegration(i v1alpha1.Integr
 
                if len(i.Sources()) > 0 {
                        w.Write(0, "Sources:\n")
-                       for _, s := range i.Sources() {
-                               w.Write(1, "Name:\t%s\n", s.Name)
-                               w.Write(1, "Content:\n")
-                               w.Write(2, "%s\n", strings.TrimSpace(s.Content))
+                       if command.showSourceContent {
+                               for _, s := range i.Sources() {
+                                       w.Write(1, "Name:\t%s\n", s.Name)
+                                       w.Write(1, "Language:\t%s\n", 
s.InferLanguage())
+                                       w.Write(1, "Compression:\t%t\n", 
s.Compression)
+                                       w.Write(1, "Content:\n")
+
+                                       if s.ContentRef == "" {
+                                               w.Write(2, "%s\n", 
strings.TrimSpace(s.Content))
+                                       } else {
+                                               w.Write(2, "Ref:\t%s\n", 
s.ContentRef)
+                                               w.Write(2, "Ref Key:\t%s\n", 
s.ContentKey)
+                                       }
+                               }
+                       } else {
+                               w.Write(1, 
"Name\tLanguage\tCompression\tRef\tRef Key\n")
+                               for _, s := range i.Sources() {
+                                       w.Write(1, "%s\t%s\t%t\t%s\t%s\n",
+                                               s.Name,
+                                               s.InferLanguage(),
+                                               s.Compression,
+                                               s.ContentRef,
+                                               s.ContentKey)
+                               }
+                       }
+               }
+
+               if len(i.Status.Conditions) > 0 {
+                       w.Write(0, "Conditions:\n")
+                       w.Write(1, "Type\tStatus\tReason\tMessage\n")
+                       for _, condition := range i.Status.Conditions {
+                               w.Write(1, "%s\t%s\t%s\t%s\n",
+                                       condition.Type,
+                                       condition.Status,
+                                       condition.Reason,
+                                       condition.Message)
                        }
                }
 
diff --git a/pkg/controller/build/build_controller.go 
b/pkg/controller/build/build_controller.go
index 3d6dac6..e582e96 100644
--- a/pkg/controller/build/build_controller.go
+++ b/pkg/controller/build/build_controller.go
@@ -167,7 +167,7 @@ func (r *ReconcileBuild) Reconcile(request 
reconcile.Request) (reconcile.Result,
        targetLog := rlog.ForBuild(target)
 
        if target.Status.Phase == v1alpha1.BuildPhaseNone || 
target.Status.Phase == v1alpha1.BuildPhaseWaitingForPlatform {
-               pl, err := platform.GetCurrentPlatform(ctx, r.client, 
target.Namespace)
+               pl, err := platform.GetOrLookup(ctx, r.client, 
target.Namespace, target.Status.Platform)
                switch {
                case err != nil:
                        target.Status.Phase = v1alpha1.BuildPhaseError
@@ -179,6 +179,10 @@ func (r *ReconcileBuild) Reconcile(request 
reconcile.Request) (reconcile.Result,
                }
 
                if instance.Status.Phase != target.Status.Phase {
+                       if pl != nil {
+                               target.SetIntegrationPlatform(pl)
+                       }
+
                        return r.update(ctx, targetLog, target)
                }
 
diff --git a/pkg/controller/integration/build_kit.go 
b/pkg/controller/integration/build_kit.go
index 0af3d82..cff1995 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -67,22 +67,23 @@ func (action *buildKitAction) Handle(ctx context.Context, 
integration *v1alpha1.
                                // We need to re-generate a kit or search for a 
new one that
                                // satisfies integrations needs so let's remove 
the association
                                // with a kit
-                               integration.Status.Kit = ""
+                               
integration.SetIntegrationKit(&v1alpha1.IntegrationKit{})
+
                                return integration, nil
                        }
                }
 
                if kit.Status.Phase == v1alpha1.IntegrationKitPhaseError {
                        integration.Status.Image = kit.ImageForIntegration()
-                       integration.Status.Kit = kit.Name
                        integration.Status.Phase = 
v1alpha1.IntegrationPhaseError
+                       integration.SetIntegrationKit(kit)
 
                        return integration, nil
                }
 
                if kit.Status.Phase == v1alpha1.IntegrationKitPhaseReady {
                        integration.Status.Image = kit.ImageForIntegration()
-                       integration.Status.Kit = kit.Name
+                       integration.SetIntegrationKit(kit)
 
                        if _, err := trait.Apply(ctx, action.client, 
integration, kit); err != nil {
                                return nil, err
@@ -92,7 +93,7 @@ func (action *buildKitAction) Handle(ctx context.Context, 
integration *v1alpha1.
                }
 
                if integration.Status.Kit == "" {
-                       integration.Status.Kit = kit.Name
+                       integration.SetIntegrationKit(kit)
 
                        return integration, nil
                }
@@ -100,12 +101,12 @@ func (action *buildKitAction) Handle(ctx context.Context, 
integration *v1alpha1.
                return nil, nil
        }
 
-       platformCtxName := fmt.Sprintf("kit-%s", xid.New())
-       platformCtx := v1alpha1.NewIntegrationKit(integration.Namespace, 
platformCtxName)
+       platformKitName := fmt.Sprintf("kit-%s", xid.New())
+       platformKit := v1alpha1.NewIntegrationKit(integration.Namespace, 
platformKitName)
 
        // Add some information for post-processing, this may need to be 
refactored
        // to a proper data structure
-       platformCtx.Labels = map[string]string{
+       platformKit.Labels = map[string]string{
                "camel.apache.org/kit.type":               
v1alpha1.IntegrationKitTypePlatform,
                "camel.apache.org/kit.created.by.kind":    
v1alpha1.IntegrationKind,
                "camel.apache.org/kit.created.by.name":    integration.Name,
@@ -113,19 +114,19 @@ func (action *buildKitAction) Handle(ctx context.Context, 
integration *v1alpha1.
        }
 
        // Set the kit to have the same characteristics as the integrations
-       platformCtx.Spec = v1alpha1.IntegrationKitSpec{
+       platformKit.Spec = v1alpha1.IntegrationKitSpec{
                Dependencies: integration.Status.Dependencies,
                Repositories: integration.Spec.Repositories,
                Traits:       integration.Spec.Traits,
        }
 
-       if err := action.client.Create(ctx, &platformCtx); err != nil {
+       if err := action.client.Create(ctx, &platformKit); err != nil {
                return nil, err
        }
 
        // Set the kit name so the next handle loop, will fall through the
        // same path as integration with a user defined kit
-       integration.Status.Kit = platformCtxName
+       integration.SetIntegrationKit(&platformKit)
 
        return integration, nil
 }
diff --git a/pkg/controller/integration/deploy.go 
b/pkg/controller/integration/deploy.go
index d61e5ec..fa03160 100644
--- a/pkg/controller/integration/deploy.go
+++ b/pkg/controller/integration/deploy.go
@@ -53,7 +53,8 @@ func (action *deployAction) Handle(ctx context.Context, 
integration *v1alpha1.In
                return nil, errors.Wrapf(err, "unable to find integration kit 
%s, %s", integration.Status.Kit, err)
        }
 
-       if _, err := trait.Apply(ctx, action.client, integration, kit); err != 
nil {
+       _, err = trait.Apply(ctx, action.client, integration, kit)
+       if err != nil {
                return nil, err
        }
 
diff --git a/pkg/controller/integration/initialize.go 
b/pkg/controller/integration/initialize.go
index 325e8e8..924fc12 100644
--- a/pkg/controller/integration/initialize.go
+++ b/pkg/controller/integration/initialize.go
@@ -50,9 +50,10 @@ func (action *initializeAction) Handle(ctx context.Context, 
integration *v1alpha
                return nil, err
        }
 
+       kit := v1alpha1.NewIntegrationKit(integration.Namespace, 
integration.Spec.Kit)
+
        integration.Status.Phase = v1alpha1.IntegrationPhaseBuildingKit
-       integration.Status.Kit = integration.Spec.Kit
-       integration.Status.Image = ""
+       integration.SetIntegrationKit(&kit)
        integration.Status.Version = defaults.Version
 
        return integration, nil
diff --git a/pkg/controller/integration/integration_controller.go 
b/pkg/controller/integration/integration_controller.go
index ef1defc..6bd6e7e 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -24,11 +24,12 @@ import (
        "github.com/apache/camel-k/pkg/util/digest"
 
        "k8s.io/apimachinery/pkg/api/errors"
-       k8serrors "k8s.io/apimachinery/pkg/api/errors"
        "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/apimachinery/pkg/types"
 
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+
        "sigs.k8s.io/controller-runtime/pkg/controller"
        "sigs.k8s.io/controller-runtime/pkg/event"
        "sigs.k8s.io/controller-runtime/pkg/handler"
@@ -78,6 +79,14 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
                UpdateFunc: func(e event.UpdateEvent) bool {
                        oldIntegration := e.ObjectOld.(*v1alpha1.Integration)
                        newIntegration := e.ObjectNew.(*v1alpha1.Integration)
+
+                       if oldIntegration.Status.Phase != 
newIntegration.Status.Phase {
+                               Log.ForIntegration(newIntegration).Info(
+                                       "Phase transition",
+                                       "old-phase", 
oldIntegration.Status.Phase,
+                                       "new-phase", 
newIntegration.Status.Phase,
+                               )
+                       }
                        // Ignore updates to the integration status in which 
case metadata.Generation does not change,
                        // or except when the integration phase changes as it's 
used to transition from one phase
                        // to another
@@ -226,16 +235,16 @@ func (r *ReconcileIntegration) Reconcile(request 
reconcile.Request) (reconcile.R
                return reconcile.Result{}, err
        }
 
-       // Delete phase
-       if instance.GetDeletionTimestamp() != nil {
-               instance.Status.Phase = v1alpha1.IntegrationPhaseDeleting
-       }
-
        target := instance.DeepCopy()
        targetLog := rlog.ForIntegration(target)
 
+       // Delete phase
+       if target.GetDeletionTimestamp() != nil {
+               target.Status.Phase = v1alpha1.IntegrationPhaseDeleting
+       }
+
        if target.Status.Phase == v1alpha1.IntegrationPhaseNone || 
target.Status.Phase == v1alpha1.IntegrationPhaseWaitingForPlatform {
-               pl, err := platform.GetCurrentPlatform(ctx, r.client, 
target.Namespace)
+               pl, err := platform.GetOrLookup(ctx, r.client, 
target.Namespace, target.Status.Platform)
                switch {
                case err != nil:
                        target.Status.Phase = v1alpha1.IntegrationPhaseError
@@ -247,6 +256,10 @@ func (r *ReconcileIntegration) Reconcile(request 
reconcile.Request) (reconcile.R
                }
 
                if instance.Status.Phase != target.Status.Phase {
+                       if pl != nil {
+                               target.SetIntegrationPlatform(pl)
+                       }
+
                        return r.update(ctx, targetLog, target)
                }
 
diff --git a/pkg/controller/integration/util.go 
b/pkg/controller/integration/util.go
index 7de88d2..2c6fce0 100644
--- a/pkg/controller/integration/util.go
+++ b/pkg/controller/integration/util.go
@@ -20,12 +20,13 @@ package integration
 import (
        "context"
 
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/util"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
 
-       k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
-
-       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/pkg/errors"
+
+       k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 var allowedLookupLabels = map[string]bool{
@@ -36,17 +37,12 @@ var allowedLookupLabels = map[string]bool{
 // LookupKitForIntegration --
 func LookupKitForIntegration(ctx context.Context, c k8sclient.Reader, 
integration *v1alpha1.Integration) (*v1alpha1.IntegrationKit, error) {
        if integration.Status.Kit != "" {
-               name := integration.Status.Kit
-               kit := v1alpha1.NewIntegrationKit(integration.Namespace, name)
-               key := k8sclient.ObjectKey{
-                       Namespace: integration.Namespace,
-                       Name:      name,
-               }
-               if err := c.Get(ctx, key, &kit); err != nil {
-                       return nil, errors.Wrapf(err, "unable to find 
integration kit %s, %s", name, err)
+               kit, err := kubernetes.GetIntegrationKit(ctx, c, 
integration.Status.Kit, integration.Namespace)
+               if err != nil {
+                       return nil, errors.Wrapf(err, "unable to find 
integration kit %s, %s", integration.Status.Kit, err)
                }
 
-               return &kit, nil
+               return kit, nil
        }
 
        ctxList := v1alpha1.NewIntegrationKitList()
diff --git a/pkg/controller/integrationkit/integrationkit_controller.go 
b/pkg/controller/integrationkit/integrationkit_controller.go
index fe14bc0..fd2d599 100644
--- a/pkg/controller/integrationkit/integrationkit_controller.go
+++ b/pkg/controller/integrationkit/integrationkit_controller.go
@@ -148,7 +148,7 @@ func (r *ReconcileIntegrationKit) Reconcile(request 
reconcile.Request) (reconcil
        targetLog := rlog.ForIntegrationKit(target)
 
        if target.Status.Phase == v1alpha1.IntegrationKitPhaseNone || 
target.Status.Phase == v1alpha1.IntegrationKitPhaseWaitingForPlatform {
-               pl, err := platform.GetCurrentPlatform(ctx, r.client, 
target.Namespace)
+               pl, err := platform.GetOrLookup(ctx, r.client, 
target.Namespace, target.Status.Platform)
                switch {
                case err != nil:
                        target.Status.Phase = v1alpha1.IntegrationKitPhaseError
@@ -160,6 +160,10 @@ func (r *ReconcileIntegrationKit) Reconcile(request 
reconcile.Request) (reconcil
                }
 
                if instance.Status.Phase != target.Status.Phase {
+                       if pl != nil {
+                               target.SetIntegrationPlatform(pl)
+                       }
+
                        return r.update(ctx, targetLog, target)
                }
 
diff --git a/pkg/platform/platform.go b/pkg/platform/platform.go
index daee58d..a8ea575 100644
--- a/pkg/platform/platform.go
+++ b/pkg/platform/platform.go
@@ -24,11 +24,25 @@ import (
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-       "github.com/apache/camel-k/pkg/client"
+       "github.com/apache/camel-k/pkg/util/kubernetes"
 )
 
+// GetOrLookup --
+func GetOrLookup(ctx context.Context, c k8sclient.Reader, namespace string, 
name string) (*v1alpha1.IntegrationPlatform, error) {
+       if name != "" {
+               return Get(ctx, c, namespace, name)
+       }
+
+       return GetCurrentPlatform(ctx, c, namespace)
+}
+
+// Get returns the currently installed platform
+func Get(ctx context.Context, c k8sclient.Reader, namespace string, name 
string) (*v1alpha1.IntegrationPlatform, error) {
+       return kubernetes.GetIntegrationPlatform(ctx, c, namespace, name)
+}
+
 // GetCurrentPlatform returns the currently installed platform
-func GetCurrentPlatform(ctx context.Context, c client.Client, namespace 
string) (*v1alpha1.IntegrationPlatform, error) {
+func GetCurrentPlatform(ctx context.Context, c k8sclient.Reader, namespace 
string) (*v1alpha1.IntegrationPlatform, error) {
        lst, err := ListPlatforms(ctx, c, namespace)
        if err != nil {
                return nil, err
@@ -44,7 +58,7 @@ func GetCurrentPlatform(ctx context.Context, c client.Client, 
namespace string)
 }
 
 // ListPlatforms returns all platforms installed in a given namespace (only 
one will be active)
-func ListPlatforms(ctx context.Context, c client.Client, namespace string) 
(*v1alpha1.IntegrationPlatformList, error) {
+func ListPlatforms(ctx context.Context, c k8sclient.Reader, namespace string) 
(*v1alpha1.IntegrationPlatformList, error) {
        lst := v1alpha1.NewIntegrationPlatformList()
        if err := c.List(ctx, &k8sclient.ListOptions{Namespace: namespace}, 
&lst); err != nil {
                return nil, err
diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go
index 35062ec..86bda93 100644
--- a/pkg/trait/deployment.go
+++ b/pkg/trait/deployment.go
@@ -40,6 +40,13 @@ func newDeploymentTrait() *deploymentTrait {
 
 func (t *deploymentTrait) Configure(e *Environment) (bool, error) {
        if t.Enabled != nil && !*t.Enabled {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionDeploymentAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionDeploymentAvailableReason,
+                       "explicitly disabled",
+               )
+
                return false, nil
        }
 
@@ -51,6 +58,12 @@ func (t *deploymentTrait) Configure(e *Environment) (bool, 
error) {
                //
                strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
                if err != nil {
+                       e.Integration.Status.SetErrorCondition(
+                               
v1alpha1.IntegrationConditionDeploymentAvailable,
+                               
v1alpha1.IntegrationConditionDeploymentAvailableReason,
+                               err,
+                       )
+
                        return false, err
                }
 
@@ -84,8 +97,18 @@ func (t *deploymentTrait) Apply(e *Environment) error {
        }
 
        if e.InPhase(v1alpha1.IntegrationKitPhaseReady, 
v1alpha1.IntegrationPhaseDeploying) {
-               e.Resources.AddAll(e.ComputeConfigMaps())
-               e.Resources.Add(t.getDeploymentFor(e))
+               maps := e.ComputeConfigMaps()
+               depl := t.getDeploymentFor(e)
+
+               e.Resources.AddAll(maps)
+               e.Resources.Add(depl)
+
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionDeploymentAvailable,
+                       corev1.ConditionTrue,
+                       v1alpha1.IntegrationConditionDeploymentAvailableReason,
+                       depl.Name,
+               )
        }
 
        return nil
diff --git a/pkg/trait/ingress.go b/pkg/trait/ingress.go
index b1184b3..8a6d68c 100644
--- a/pkg/trait/ingress.go
+++ b/pkg/trait/ingress.go
@@ -19,12 +19,15 @@ package trait
 
 import (
        "errors"
+       "fmt"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-       corev1 "k8s.io/api/core/v1"
+
        "k8s.io/api/extensions/v1beta1"
-       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/util/intstr"
+
+       corev1 "k8s.io/api/core/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 type ingressTrait struct {
@@ -42,6 +45,12 @@ func newIngressTrait() *ingressTrait {
 
 func (t *ingressTrait) Configure(e *Environment) (bool, error) {
        if t.Enabled != nil && !*t.Enabled {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionExposureAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionIngressNotAvailableReason,
+                       "explicitly disabled",
+               )
                return false, nil
        }
 
@@ -50,16 +59,30 @@ func (t *ingressTrait) Configure(e *Environment) (bool, 
error) {
        }
 
        if t.Auto == nil || *t.Auto {
-               hasService := t.getTargetService(e) != nil
+               hasService := 
e.Resources.GetUserServiceForIntegration(e.Integration) != nil
                hasHost := t.Host != ""
                enabled := hasService && hasHost
 
                if !enabled {
+                       e.Integration.Status.SetCondition(
+                               v1alpha1.IntegrationConditionExposureAvailable,
+                               corev1.ConditionFalse,
+                               
v1alpha1.IntegrationConditionIngressNotAvailableReason,
+                               "no host or service defined",
+                       )
+
                        return false, nil
                }
        }
 
        if t.Host == "" {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionExposureAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionIngressNotAvailableReason,
+                       "no host defined",
+               )
+
                return false, errors.New("cannot Apply ingress trait: no host 
defined")
        }
 
@@ -67,33 +90,11 @@ func (t *ingressTrait) Configure(e *Environment) (bool, 
error) {
 }
 
 func (t *ingressTrait) Apply(e *Environment) error {
-       if t.Host == "" {
-               return errors.New("cannot Apply ingress trait: no host defined")
-       }
-       service := t.getTargetService(e)
+       service := e.Resources.GetUserServiceForIntegration(e.Integration)
        if service == nil {
                return errors.New("cannot Apply ingress trait: no target 
service")
        }
 
-       e.Resources.Add(t.getIngressFor(service))
-       return nil
-}
-
-func (t *ingressTrait) getTargetService(e *Environment) (service 
*corev1.Service) {
-       e.Resources.VisitService(func(s *corev1.Service) {
-               if s.ObjectMeta.Labels != nil {
-                       if s.ObjectMeta.Labels["camel.apache.org/integration"] 
== e.Integration.Name &&
-                               
s.ObjectMeta.Labels["camel.apache.org/service.type"] == ServiceTypeUser {
-                               // We should build an ingress only on top of 
the user service (e.g. not if the service contains
-                               // only prometheus)
-                               service = s
-                       }
-               }
-       })
-       return
-}
-
-func (t *ingressTrait) getIngressFor(service *corev1.Service) *v1beta1.Ingress 
{
        ingress := v1beta1.Ingress{
                TypeMeta: metav1.TypeMeta{
                        Kind:       "Ingress",
@@ -115,5 +116,21 @@ func (t *ingressTrait) getIngressFor(service 
*corev1.Service) *v1beta1.Ingress {
                        },
                },
        }
-       return &ingress
+
+       e.Resources.Add(&ingress)
+
+       message := fmt.Sprintf("%s(%s) -> %s(%s)",
+               ingress.Name,
+               t.Host,
+               ingress.Spec.Backend.ServiceName,
+               ingress.Spec.Backend.ServicePort.String())
+
+       e.Integration.Status.SetCondition(
+               v1alpha1.IntegrationConditionExposureAvailable,
+               corev1.ConditionTrue,
+               v1alpha1.IntegrationConditionIngressAvailableReason,
+               message,
+       )
+
+       return nil
 }
diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go
index 0835a56..db674de 100644
--- a/pkg/trait/knative_service.go
+++ b/pkg/trait/knative_service.go
@@ -27,7 +27,6 @@ import (
        "github.com/apache/camel-k/pkg/metadata"
        "github.com/apache/camel-k/pkg/util/envvar"
        serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
-       appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -59,6 +58,13 @@ func newKnativeServiceTrait() *knativeServiceTrait {
 
 func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {
        if t.Enabled != nil && !*t.Enabled {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionKnativeServiceAvailable,
+                       corev1.ConditionFalse,
+                       
v1alpha1.IntegrationConditionKnativeServiceNotAvailableReason,
+                       "explicitly disabled",
+               )
+
                return false, nil
        }
 
@@ -66,22 +72,36 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
                return false, nil
        }
 
+       if e.Resources.GetDeploymentForIntegration(e.Integration) != nil {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionKnativeServiceAvailable,
+                       corev1.ConditionFalse,
+                       
v1alpha1.IntegrationConditionKnativeServiceNotAvailableReason,
+                       "controller strategy: "+ControllerStrategyDeployment,
+               )
+
+               // A controller is already present for the integration
+               return false, nil
+       }
+
        strategy, err := e.DetermineControllerStrategy(t.ctx, t.client)
        if err != nil {
+               e.Integration.Status.SetErrorCondition(
+                       v1alpha1.IntegrationConditionKnativeServiceAvailable,
+                       
v1alpha1.IntegrationConditionKnativeServiceNotAvailableReason,
+                       err,
+               )
+
                return false, err
        }
        if strategy != ControllerStrategyKnativeService {
-               return false, nil
-       }
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionKnativeServiceAvailable,
+                       corev1.ConditionFalse,
+                       
v1alpha1.IntegrationConditionKnativeServiceNotAvailableReason,
+                       "controller strategy: "+string(strategy),
+               )
 
-       deployment := e.Resources.GetDeployment(func(d *appsv1.Deployment) bool 
{
-               if name, ok := 
d.ObjectMeta.Labels["camel.apache.org/integration"]; ok {
-                       return name == e.Integration.Name
-               }
-               return false
-       })
-       if deployment != nil {
-               // A controller is already present for the integration
                return false, nil
        }
 
@@ -90,6 +110,12 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
                if t.MinScale == nil {
                        sources, err := 
kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, 
e.Resources)
                        if err != nil {
+                               e.Integration.Status.SetErrorCondition(
+                                       
v1alpha1.IntegrationConditionKnativeServiceAvailable,
+                                       
v1alpha1.IntegrationConditionKnativeServiceNotAvailableReason,
+                                       err,
+                               )
+
                                return false, err
                        }
 
@@ -110,11 +136,18 @@ func (t *knativeServiceTrait) Configure(e *Environment) 
(bool, error) {
 }
 
 func (t *knativeServiceTrait) Apply(e *Environment) error {
-       svc := t.getServiceFor(e)
+       ksvc := t.getServiceFor(e)
        maps := e.ComputeConfigMaps()
 
-       e.Resources.Add(svc)
        e.Resources.AddAll(maps)
+       e.Resources.Add(ksvc)
+
+       e.Integration.Status.SetCondition(
+               v1alpha1.IntegrationConditionDeploymentAvailable,
+               corev1.ConditionTrue,
+               v1alpha1.IntegrationConditionKnativeServiceAvailableReason,
+               ksvc.Name,
+       )
 
        return nil
 }
diff --git a/pkg/trait/route.go b/pkg/trait/route.go
index 2d53872..c3eb320 100644
--- a/pkg/trait/route.go
+++ b/pkg/trait/route.go
@@ -18,6 +18,7 @@ limitations under the License.
 package trait
 
 import (
+       "fmt"
        "reflect"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -49,6 +50,13 @@ func newRouteTrait() *routeTrait {
 
 func (t *routeTrait) Configure(e *Environment) (bool, error) {
        if t.Enabled != nil && !*t.Enabled {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionExposureAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionRouteNotAvailableReason,
+                       "explicitly disabled",
+               )
+
                return false, nil
        }
 
@@ -56,8 +64,15 @@ func (t *routeTrait) Configure(e *Environment) (bool, error) 
{
                return false, nil
        }
 
-       t.service = t.getTargetService(e)
+       t.service = e.Resources.GetUserServiceForIntegration(e.Integration)
        if t.service == nil {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionExposureAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionRouteNotAvailableReason,
+                       "no target service found",
+               )
+
                return false, nil
        }
 
@@ -65,41 +80,14 @@ func (t *routeTrait) Configure(e *Environment) (bool, 
error) {
 }
 
 func (t *routeTrait) Apply(e *Environment) error {
-       if e.Integration == nil || e.Integration.Status.Phase != 
v1alpha1.IntegrationPhaseDeploying {
-               return nil
-       }
-
-       e.Resources.Add(t.getRouteFor(t.service))
-       return nil
-}
-
-func (t *routeTrait) getTargetService(e *Environment) *corev1.Service {
-       var service *corev1.Service
-
-       e.Resources.VisitService(func(s *corev1.Service) {
-               if s.ObjectMeta.Labels != nil {
-                       if s.ObjectMeta.Labels["camel.apache.org/integration"] 
== e.Integration.Name &&
-                               
s.ObjectMeta.Labels["camel.apache.org/service.type"] == ServiceTypeUser {
-
-                               // We should build a route only on top of the 
user service (e.g. not if the service contains
-                               // only prometheus)
-                               service = s
-                       }
-               }
-       })
-
-       return service
-}
-
-func (t *routeTrait) getRouteFor(service *corev1.Service) *routev1.Route {
        route := routev1.Route{
                TypeMeta: metav1.TypeMeta{
                        Kind:       "Route",
                        APIVersion: routev1.SchemeGroupVersion.String(),
                },
                ObjectMeta: metav1.ObjectMeta{
-                       Name:      service.Name,
-                       Namespace: service.Namespace,
+                       Name:      t.service.Name,
+                       Namespace: t.service.Namespace,
                },
                Spec: routev1.RouteSpec{
                        Port: &routev1.RoutePort{
@@ -107,13 +95,38 @@ func (t *routeTrait) getRouteFor(service *corev1.Service) 
*routev1.Route {
                        },
                        To: routev1.RouteTargetReference{
                                Kind: "Service",
-                               Name: service.Name,
+                               Name: t.service.Name,
                        },
                        Host: t.Host,
                        TLS:  t.getTLSConfig(),
                },
        }
-       return &route
+
+       e.Resources.Add(&route)
+
+       var message string
+
+       if t.Host == "" {
+               message = fmt.Sprintf("%s -> %s(%s)",
+                       route.Name,
+                       route.Spec.To.Name,
+                       route.Spec.Port.TargetPort.String())
+       } else {
+               message = fmt.Sprintf("%s(%s) -> %s(%s)",
+                       route.Name,
+                       t.Host,
+                       route.Spec.To.Name,
+                       route.Spec.Port.TargetPort.String())
+       }
+
+       e.Integration.Status.SetCondition(
+               v1alpha1.IntegrationConditionExposureAvailable,
+               corev1.ConditionTrue,
+               v1alpha1.IntegrationConditionRouteAvailableReason,
+               message,
+       )
+
+       return nil
 }
 
 func (t *routeTrait) getTLSConfig() *routev1.TLSConfig {
diff --git a/pkg/trait/route_test.go b/pkg/trait/route_test.go
index 48310fd..0f81683 100644
--- a/pkg/trait/route_test.go
+++ b/pkg/trait/route_test.go
@@ -81,7 +81,7 @@ func createTestRouteEnvironment(t *testing.T, name string) 
*Environment {
                                        Namespace: "test-ns",
                                        Labels: map[string]string{
                                                "camel.apache.org/integration": 
 name,
-                                               
"camel.apache.org/service.type": ServiceTypeUser,
+                                               
"camel.apache.org/service.type": v1alpha1.ServiceTypeUser,
                                        },
                                },
                                Spec: corev1.ServiceSpec{
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 64f410e..3bcbcc4 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -18,10 +18,11 @@ 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"
-       "github.com/pkg/errors"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/util/intstr"
@@ -51,6 +52,13 @@ func newServiceTrait() *serviceTrait {
 
 func (t *serviceTrait) Configure(e *Environment) (bool, error) {
        if t.Enabled != nil && !*t.Enabled {
+               e.Integration.Status.SetCondition(
+                       v1alpha1.IntegrationConditionServiceAvailable,
+                       corev1.ConditionFalse,
+                       v1alpha1.IntegrationConditionServiceNotAvailableReason,
+                       "explicitly disabled",
+               )
+
                return false, nil
        }
 
@@ -61,10 +69,25 @@ func (t *serviceTrait) Configure(e *Environment) (bool, 
error) {
        if t.Auto == nil || *t.Auto {
                sources, err := kubernetes.ResolveIntegrationSources(t.ctx, 
t.client, e.Integration, e.Resources)
                if err != nil {
+                       e.Integration.Status.SetCondition(
+                               v1alpha1.IntegrationConditionServiceAvailable,
+                               corev1.ConditionFalse,
+                               
v1alpha1.IntegrationConditionServiceNotAvailableReason,
+                               err.Error(),
+                       )
+
                        return false, err
                }
+
                meta := metadata.ExtractAll(e.CamelCatalog, sources)
                if !meta.RequiresHTTPService {
+                       e.Integration.Status.SetCondition(
+                               v1alpha1.IntegrationConditionServiceAvailable,
+                               corev1.ConditionFalse,
+                               
v1alpha1.IntegrationConditionServiceNotAvailableReason,
+                               "no http service required",
+                       )
+
                        return false, nil
                }
        }
@@ -91,24 +114,34 @@ func (t *serviceTrait) Apply(e *Environment) (err error) {
        svc.Spec.Ports = append(svc.Spec.Ports, port)
 
        // Mark the service as a user service
-       svc.Labels["camel.apache.org/service.type"] = ServiceTypeUser
+       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 {
-               var container *corev1.Container
-               environment.Resources.VisitContainer(func(c *corev1.Container) {
-                       if c.Name == environment.Integration.Name {
-                               container = c
-                       }
+               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 errors.New("Cannot add HTTP container port: no 
integration container")
+                       return fmt.Errorf("cannot add %s container port: no 
integration container", t.ContainerPortName)
                }
                return nil
        })
diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go
index 63b55bb..3399057 100644
--- a/pkg/trait/trait_types.go
+++ b/pkg/trait/trait_types.go
@@ -28,6 +28,8 @@ import (
 
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       controller "sigs.k8s.io/controller-runtime/pkg/client"
+
        "k8s.io/apimachinery/pkg/runtime"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
@@ -43,9 +45,6 @@ import (
 // True --
 const True = "true"
 
-// ServiceTypeUser --
-const ServiceTypeUser = "user"
-
 // Identifiable represent an identifiable type
 type Identifiable interface {
        ID() ID
@@ -196,7 +195,7 @@ func (e *Environment) DetermineProfile() 
v1alpha1.TraitProfile {
 }
 
 // DetermineControllerStrategy determines the type of controller that should 
be used for the integration
-func (e *Environment) DetermineControllerStrategy(ctx context.Context, c 
client.Client) (ControllerStrategy, error) {
+func (e *Environment) DetermineControllerStrategy(ctx context.Context, c 
controller.Reader) (ControllerStrategy, error) {
        if e.DetermineProfile() != v1alpha1.TraitProfileKnative {
                return ControllerStrategyDeployment, nil
        }
diff --git a/pkg/util/indentedwriter/writer.go 
b/pkg/util/indentedwriter/writer.go
index 99259be..cab0e8c 100644
--- a/pkg/util/indentedwriter/writer.go
+++ b/pkg/util/indentedwriter/writer.go
@@ -21,6 +21,7 @@ import (
        "bytes"
        "fmt"
        "io"
+       "strings"
        "text/tabwriter"
 )
 
@@ -41,12 +42,15 @@ func NewWriter(out io.Writer) *Writer {
 
 // Write --
 func (iw *Writer) Write(indentLevel int, format string, i ...interface{}) {
-       indent := "  "
-       prefix := ""
-       for i := 0; i < indentLevel; i++ {
-               prefix += indent
-       }
-       fmt.Fprintf(iw.out, prefix+format, i...)
+       fmt.Fprint(iw.out, strings.Repeat("  ", indentLevel))
+       fmt.Fprintf(iw.out, format, i...)
+}
+
+// Writeln --
+func (iw *Writer) Writeln(indentLevel int, format string, i ...interface{}) {
+       fmt.Fprint(iw.out, strings.Repeat("  ", indentLevel))
+       fmt.Fprintf(iw.out, format, i...)
+       fmt.Fprint(iw.out, "\n")
 }
 
 // Flush --
diff --git a/pkg/util/kubernetes/collection.go 
b/pkg/util/kubernetes/collection.go
index 6eff785..418b2db 100644
--- a/pkg/util/kubernetes/collection.go
+++ b/pkg/util/kubernetes/collection.go
@@ -18,6 +18,7 @@ limitations under the License.
 package kubernetes
 
 import (
+       "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        serving "github.com/knative/serving/pkg/apis/serving/v1alpha1"
        routev1 "github.com/openshift/api/route/v1"
        appsv1 "k8s.io/api/apps/v1"
@@ -101,6 +102,17 @@ func (c *Collection) GetDeployment(filter 
func(*appsv1.Deployment) bool) *appsv1
        return retValue
 }
 
+// GetDeploymentForIntegration returns a Deployment for the given integration
+func (c *Collection) GetDeploymentForIntegration(integration 
*v1alpha1.Integration) *appsv1.Deployment {
+       if integration == nil {
+               return nil
+       }
+
+       return c.GetDeployment(func(d *appsv1.Deployment) bool {
+               return d.ObjectMeta.Labels["camel.apache.org/integration"] == 
integration.Name
+       })
+}
+
 // HasDeployment returns true if a deployment matching the given condition is 
present
 func (c *Collection) HasDeployment(filter func(*appsv1.Deployment) bool) bool {
        return c.GetDeployment(filter) != nil
@@ -174,6 +186,18 @@ func (c *Collection) GetService(filter 
func(*corev1.Service) bool) *corev1.Servi
        return retValue
 }
 
+// GetUserServiceForIntegration returns a user Service for the given 
integration
+func (c *Collection) GetUserServiceForIntegration(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 &&
+                       s.ObjectMeta.Labels["camel.apache.org/service.type"] == 
v1alpha1.ServiceTypeUser
+       })
+}
+
 // 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
@@ -214,6 +238,19 @@ func (c *Collection) VisitKnativeService(visitor 
func(*serving.Service)) {
        })
 }
 
+// GetContainer --
+func (c *Collection) GetContainer(filter func(container *corev1.Container) 
bool) *corev1.Container {
+       var retValue *corev1.Container
+
+       c.VisitContainer(func(container *corev1.Container) {
+               if filter(container) {
+                       retValue = container
+               }
+       })
+
+       return retValue
+}
+
 // 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) {
diff --git a/pkg/util/kubernetes/resolver.go b/pkg/util/kubernetes/resolver.go
index d2ad8fc..3303409 100644
--- a/pkg/util/kubernetes/resolver.go
+++ b/pkg/util/kubernetes/resolver.go
@@ -22,8 +22,9 @@ import (
        "fmt"
 
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
-       "github.com/apache/camel-k/pkg/client"
+
        corev1 "k8s.io/api/core/v1"
+       controller "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 // ResolveSources --
@@ -78,7 +79,12 @@ func Resolve(data *v1alpha1.DataSpec, mapLookup func(string) 
(*corev1.ConfigMap,
 }
 
 // ResolveIntegrationSources --
-func ResolveIntegrationSources(context context.Context, client client.Client, 
integration *v1alpha1.Integration, resources *Collection) 
([]v1alpha1.SourceSpec, error) {
+func ResolveIntegrationSources(
+       context context.Context,
+       client controller.Reader,
+       integration *v1alpha1.Integration,
+       resources *Collection) ([]v1alpha1.SourceSpec, error) {
+
        if integration == nil {
                return nil, nil
        }
@@ -100,7 +106,12 @@ func ResolveIntegrationSources(context context.Context, 
client client.Client, in
 
 // ResolveIntegrationResources --
 // nolint: lll
-func ResolveIntegrationResources(context context.Context, client 
client.Client, integration *v1alpha1.Integration, resources *Collection) 
([]v1alpha1.ResourceSpec, error) {
+func ResolveIntegrationResources(
+       context context.Context,
+       client controller.Reader,
+       integration *v1alpha1.Integration,
+       resources *Collection) ([]v1alpha1.ResourceSpec, error) {
+
        if integration == nil {
                return nil, nil
        }
diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go
index 7f1df15..7b995fb 100644
--- a/pkg/util/kubernetes/util.go
+++ b/pkg/util/kubernetes/util.go
@@ -70,7 +70,7 @@ func JSONToYAML(src []byte) ([]byte, error) {
 }
 
 // GetConfigMap --
-func GetConfigMap(context context.Context, client client.Client, name string, 
namespace string) (*corev1.ConfigMap, error) {
+func GetConfigMap(context context.Context, client k8sclient.Reader, name 
string, namespace string) (*corev1.ConfigMap, error) {
        key := k8sclient.ObjectKey{
                Name:      name,
                Namespace: namespace,
@@ -95,7 +95,7 @@ func GetConfigMap(context context.Context, client 
client.Client, name string, na
 }
 
 // GetSecret --
-func GetSecret(context context.Context, client client.Client, name string, 
namespace string) (*corev1.Secret, error) {
+func GetSecret(context context.Context, client k8sclient.Reader, name string, 
namespace string) (*corev1.Secret, error) {
        key := k8sclient.ObjectKey{
                Name:      name,
                Namespace: namespace,
@@ -119,8 +119,24 @@ func GetSecret(context context.Context, client 
client.Client, name string, names
        return &answer, nil
 }
 
+// GetIntegrationPlatform --
+func GetIntegrationPlatform(context context.Context, client k8sclient.Reader, 
name string, namespace string) (*v1alpha1.IntegrationPlatform, error) {
+       key := k8sclient.ObjectKey{
+               Name:      name,
+               Namespace: namespace,
+       }
+
+       answer := v1alpha1.NewIntegrationPlatform(namespace, name)
+
+       if err := client.Get(context, key, &answer); err != nil {
+               return nil, err
+       }
+
+       return &answer, nil
+}
+
 // GetIntegrationKit --
-func GetIntegrationKit(context context.Context, client client.Client, name 
string, namespace string) (*v1alpha1.IntegrationKit, error) {
+func GetIntegrationKit(context context.Context, client k8sclient.Reader, name 
string, namespace string) (*v1alpha1.IntegrationKit, error) {
        key := k8sclient.ObjectKey{
                Name:      name,
                Namespace: namespace,
@@ -136,7 +152,7 @@ func GetIntegrationKit(context context.Context, client 
client.Client, name strin
 }
 
 // GetIntegration --
-func GetIntegration(context context.Context, client client.Client, name 
string, namespace string) (*v1alpha1.Integration, error) {
+func GetIntegration(context context.Context, client k8sclient.Reader, name 
string, namespace string) (*v1alpha1.Integration, error) {
        key := k8sclient.ObjectKey{
                Name:      name,
                Namespace: namespace,
@@ -168,7 +184,7 @@ func GetBuild(context context.Context, client 
client.Client, name string, namesp
 }
 
 // GetService --
-func GetService(context context.Context, client client.Client, name string, 
namespace string) (*corev1.Service, error) {
+func GetService(context context.Context, client k8sclient.Reader, name string, 
namespace string) (*corev1.Service, error) {
        key := k8sclient.ObjectKey{
                Name:      name,
                Namespace: namespace,
@@ -255,7 +271,7 @@ func LookUpResources(ctx context.Context, client 
client.Client, namespace string
 }
 
 // GetSecretRefValue returns the value of a secret in the supplied namespace --
-func GetSecretRefValue(ctx context.Context, client client.Client, namespace 
string, selector *corev1.SecretKeySelector) (string, error) {
+func GetSecretRefValue(ctx context.Context, client k8sclient.Reader, namespace 
string, selector *corev1.SecretKeySelector) (string, error) {
        secret, err := GetSecret(ctx, client, selector.Name, namespace)
        if err != nil {
                return "", err
@@ -270,7 +286,7 @@ func GetSecretRefValue(ctx context.Context, client 
client.Client, namespace stri
 }
 
 // GetConfigMapRefValue returns the value of a configmap in the supplied 
namespace
-func GetConfigMapRefValue(ctx context.Context, client client.Client, namespace 
string, selector *corev1.ConfigMapKeySelector) (string, error) {
+func GetConfigMapRefValue(ctx context.Context, client k8sclient.Reader, 
namespace string, selector *corev1.ConfigMapKeySelector) (string, error) {
        cm, err := GetConfigMap(ctx, client, selector.Name, namespace)
        if err != nil {
                return "", err
@@ -284,7 +300,7 @@ func GetConfigMapRefValue(ctx context.Context, client 
client.Client, namespace s
 }
 
 // ResolveValueSource --
-func ResolveValueSource(ctx context.Context, client client.Client, namespace 
string, valueSource *v1alpha1.ValueSource) (string, error) {
+func ResolveValueSource(ctx context.Context, client k8sclient.Reader, 
namespace string, valueSource *v1alpha1.ValueSource) (string, error) {
        if valueSource.ConfigMapKeyRef != nil && valueSource.SecretKeyRef != 
nil {
                return "", fmt.Errorf("value source has bot config map and 
secret configured")
        }

Reply via email to