diff --git a/pkg/apis/camel/v1alpha1/build_types.go b/pkg/apis/camel/v1alpha1/build_types.go index 8f8e90a397..43b4660a1a 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 a49a4aa738..8aae4ada56 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 1c57c626af..cf74f69354 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 f94320d5fe..b6435afd07 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 e7e089c78e..1f72a5e52a 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 f4c584fa6c..0228859e9e 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 46ce1fe51b..558cd96cc1 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 e997d2cd80..9a1e047fa5 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 a5832446c4..ff35966544 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 43e14e8bd1..5f31fa6e26 100644 --- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go @@ -53,6 +53,24 @@ func (in *Build) DeepCopyObject() runtime.Object { return nil } +// 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 @@ -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 } @@ -438,6 +463,24 @@ func (in *Integration) DeepCopyObject() runtime.Object { return nil } +// 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 @@ -466,6 +509,24 @@ func (in *IntegrationKit) DeepCopyObject() runtime.Object { return nil } +// 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 @@ -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 } @@ -650,6 +718,24 @@ func (in *IntegrationPlatformBuildSpec) DeepCopy() *IntegrationPlatformBuildSpec return out } +// 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 @@ -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 5d70cfddf4..117868fa66 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 3d6dac634e..e582e965b8 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 0af3d82d7a..cff1995976 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 d61e5ec806..fa03160321 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 325e8e8466..924fc12bc9 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 ef1defc66d..641eaa3dad 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" @@ -226,16 +227,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 +248,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 7de88d242c..2c6fce0f49 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 fe14bc06db..fd2d59910a 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 daee58da5b..a8ea5752bb 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 35062ecaa8..86bda93bea 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 b1184b39d5..8a6d68c490 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 0835a5634c..db674de02f 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 2d53872f91..c3eb320c5e 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 48310fd6af..0f81683d52 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 64f410e3a6..3bcbcc44f8 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 63b55bbe9a..33990572b7 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 99259bec13..cab0e8cfa7 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 6eff78572f..418b2db93c 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 d2ad8fc9b8..33034096e1 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 7f1df15af1..7b995fb2d8 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") }