From commits-return-65773-archive-asf-public=cust-asf.ponee.io@camel.apache.org Mon Oct 8 16:19:48 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 50AD018067E for ; Mon, 8 Oct 2018 16:19:47 +0200 (CEST) Received: (qmail 2675 invoked by uid 500); 8 Oct 2018 14:19:41 -0000 Mailing-List: contact commits-help@camel.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@camel.apache.org Delivered-To: mailing list commits@camel.apache.org Received: (qmail 2564 invoked by uid 99); 8 Oct 2018 14:19:41 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 08 Oct 2018 14:19:41 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id B9C9582C6A; Mon, 8 Oct 2018 14:19:40 +0000 (UTC) Date: Mon, 08 Oct 2018 14:19:42 +0000 To: "commits@camel.apache.org" Subject: [camel-k] 02/14: Moving logic to the trait area MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: lburgazzoli@apache.org In-Reply-To: <153900838033.12376.7955296584845618391@gitbox.apache.org> References: <153900838033.12376.7955296584845618391@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: camel-k X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Rev: 3bbfaad7fbb88453bcd2e5bae7af18ebd2419c7e X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20181008141940.B9C9582C6A@gitbox.apache.org> 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 3bbfaad7fbb88453bcd2e5bae7af18ebd2419c7e Author: nferraro AuthorDate: Thu Oct 4 12:12:56 2018 +0200 Moving logic to the trait area --- pkg/stub/action/integration/deploy.go | 281 ++------------------- pkg/stub/action/integration/util.go | 83 ------ .../action/integration/deploy.go => trait/base.go} | 158 +++--------- pkg/trait/{catalog => }/catalog.go | 64 +++-- pkg/trait/{catalog => }/expose.go | 9 +- pkg/trait/{catalog => }/identity.go | 9 +- pkg/trait/owner.go | 48 ++++ pkg/trait/trait.go | 35 ++- pkg/{stub/action/integration => trait}/util.go | 50 +--- pkg/util/kubernetes/collection.go | 19 +- 10 files changed, 213 insertions(+), 543 deletions(-) diff --git a/pkg/stub/action/integration/deploy.go b/pkg/stub/action/integration/deploy.go index 8067b3c..c9c7f4e 100644 --- a/pkg/stub/action/integration/deploy.go +++ b/pkg/stub/action/integration/deploy.go @@ -18,17 +18,14 @@ limitations under the License. package integration import ( - "fmt" - "github.com/sirupsen/logrus" - "strings" - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/trait" + "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/operator-framework/operator-sdk/pkg/sdk" "github.com/pkg/errors" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" + "github.com/sirupsen/logrus" k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // NewDeployAction create an action that handles integration deploy @@ -48,15 +45,18 @@ func (action *deployAction) CanHandle(integration *v1alpha1.Integration) bool { } func (action *deployAction) Handle(integration *v1alpha1.Integration) error { - ctx, err := LookupContextForIntegration(integration) + environment, err := trait.NewEnvironment(integration) if err != nil { return err } - err = createOrUpdateConfigMap(ctx, integration) - if err != nil { - return err + resources := kubernetes.NewCollection() + customizers := trait.CustomizersFor(*environment) + // invoke the trait framework to determine the needed resources + if _, err = customizers.Customize(*environment, resources); err != nil { + return errors.Wrap(err, "error during trait customization") } - err = createOrUpdateDeployment(ctx, integration) + // TODO we should look for objects that are no longer present in the collection and remove them + err = action.createOrUpdateObjects(resources.Items(), integration) if err != nil { return err } @@ -68,254 +68,15 @@ func (action *deployAction) Handle(integration *v1alpha1.Integration) error { return sdk.Update(target) } -// ********************************** -// -// ConfigMap -// -// ********************************** - -func getConfigMapFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) (*corev1.ConfigMap, error) { - controller := true - blockOwnerDeletion := true - - // combine properties of integration with context, integration - // properties have the priority - properties := CombineConfigurationAsMap("property", ctx, integration) - - cm := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: integration.Name, - Namespace: integration.Namespace, - Labels: integration.Labels, - Annotations: map[string]string{ - "camel.apache.org/source.language": string(integration.Spec.Source.Language), - "camel.apache.org/source.name": integration.Spec.Source.Name, - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: integration.APIVersion, - Kind: integration.Kind, - Name: integration.Name, - UID: integration.UID, - Controller: &controller, - BlockOwnerDeletion: &blockOwnerDeletion, - }, - }, - }, - Data: map[string]string{ - "integration": integration.Spec.Source.Content, - "properties": PropertiesString(properties), - }, - } - - return &cm, nil -} - -func createOrUpdateConfigMap(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) error { - cm, err := getConfigMapFor(ctx, integration) - if err != nil { - return err - } - - err = sdk.Create(cm) - if err != nil && k8serrors.IsAlreadyExists(err) { - err = sdk.Update(cm) - } - if err != nil { - return errors.Wrap(err, "could not create or replace configmap for integration "+integration.Name) - } - - return err -} - -// ********************************** -// -// Deployment -// -// ********************************** - -func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) (*appsv1.Deployment, error) { - controller := true - blockOwnerDeletion := true - sourceName := strings.TrimPrefix(integration.Spec.Source.Name, "/") - - // combine environment of integration with context, integration - // environment has the priority - environment := CombineConfigurationAsMap("env", ctx, integration) - - // set env vars needed by the runtime - environment["JAVA_MAIN_CLASS"] = "org.apache.camel.k.jvm.Application" - - // camel-k runtime - environment["CAMEL_K_ROUTES_URI"] = "file:/etc/camel/conf/" + sourceName - environment["CAMEL_K_ROUTES_LANGUAGE"] = string(integration.Spec.Source.Language) - environment["CAMEL_K_CONF"] = "/etc/camel/conf/application.properties" - environment["CAMEL_K_CONF_D"] = "/etc/camel/conf.d" - - // add a dummy env var to trigger deployment if everything but the code - // has been changed - environment["CAMEL_K_DIGEST"] = integration.Status.Digest - - // optimizations - environment["AB_JOLOKIA_OFF"] = "true" - - labels := map[string]string{ - "camel.apache.org/integration": integration.Name, - } - deployment := appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - APIVersion: appsv1.SchemeGroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: integration.Name, - Namespace: integration.Namespace, - Labels: integration.Labels, - Annotations: integration.Annotations, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: integration.APIVersion, - Kind: integration.Kind, - Name: integration.Name, - Controller: &controller, - BlockOwnerDeletion: &blockOwnerDeletion, - UID: integration.UID, - }, - }, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: integration.Spec.Replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: integration.Name, - Image: integration.Status.Image, - Env: EnvironmentAsEnvVarSlice(environment), - }, - }, - }, - }, - }, - } - - // - // Volumes :: Setup - // - - vols := make([]corev1.Volume, 0) - mnts := make([]corev1.VolumeMount, 0) - cnt := 0 - - // - // Volumes :: Defaults - // - - vols = append(vols, corev1.Volume{ - Name: "integration", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: integration.Name, - }, - Items: []corev1.KeyToPath{ - { - Key: "integration", - Path: sourceName, - }, { - Key: "properties", - Path: "application.properties", - }, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: "integration", - MountPath: "/etc/camel/conf", - }) - - // - // Volumes :: Additional ConfigMaps - // - - cmList := CombineConfigurationAsSlice("configmap", ctx, integration) - for _, cmName := range cmList { - cnt++ - - vols = append(vols, corev1.Volume{ - Name: "integration-cm-" + cmName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cmName, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: "integration-cm-" + cmName, - MountPath: fmt.Sprintf("/etc/camel/conf.d/%03d_%s", cnt, cmName), - }) - } - - // - // Volumes :: Additional Secrets - // - - secretList := CombineConfigurationAsSlice("secret", ctx, integration) - for _, secretName := range secretList { - cnt++ - - vols = append(vols, corev1.Volume{ - Name: "integration-secret-" + secretName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: "integration-secret-" + secretName, - MountPath: fmt.Sprintf("/etc/camel/conf.d/%03d_%s", cnt, secretName), - }) - } - - // - // Volumes - // - - deployment.Spec.Template.Spec.Volumes = vols - deployment.Spec.Template.Spec.Containers[0].VolumeMounts = mnts - - return &deployment, nil -} - -func createOrUpdateDeployment(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) error { - deployment, err := getDeploymentFor(ctx, integration) - if err != nil { - return err - } - - err = sdk.Create(deployment) - if err != nil && k8serrors.IsAlreadyExists(err) { - err = sdk.Update(deployment) - } - if err != nil { - return errors.Wrap(err, "could not create or replace deployment for integration "+integration.Name) +func (action *deployAction) createOrUpdateObjects(objects []runtime.Object, integration *v1alpha1.Integration) error { + for _, object := range objects { + err := sdk.Create(object) + if err != nil && k8serrors.IsAlreadyExists(err) { + err = sdk.Update(object) + } + if err != nil { + return errors.Wrap(err, "could not create or replace resource for integration "+integration.Name) + } } return nil diff --git a/pkg/stub/action/integration/util.go b/pkg/stub/action/integration/util.go index ab9cf79..27bfb29 100644 --- a/pkg/stub/action/integration/util.go +++ b/pkg/stub/action/integration/util.go @@ -18,15 +18,10 @@ limitations under the License. package integration import ( - "fmt" - "strings" - "github.com/apache/camel-k/pkg/util" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/operator-framework/operator-sdk/pkg/sdk" - "k8s.io/api/core/v1" - "github.com/pkg/errors" ) @@ -65,81 +60,3 @@ func LookupContextForIntegration(integration *v1alpha1.Integration) (*v1alpha1.I return nil, nil } - -// PropertiesString -- -func PropertiesString(m map[string]string) string { - properties := "" - for k, v := range m { - properties += fmt.Sprintf("%s=%s\n", k, v) - } - - return properties -} - -// EnvironmentAsEnvVarSlice -- -func EnvironmentAsEnvVarSlice(m map[string]string) []v1.EnvVar { - env := make([]v1.EnvVar, 0, len(m)) - - for k, v := range m { - env = append(env, v1.EnvVar{Name: k, Value: v}) - } - - return env -} - -// CombineConfigurationAsMap -- -func CombineConfigurationAsMap(configurationType string, context *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) map[string]string { - result := make(map[string]string) - if context != nil { - // Add context properties first so integrations can - // override it - for _, c := range context.Spec.Configuration { - if c.Type == configurationType { - pair := strings.Split(c.Value, "=") - if len(pair) == 2 { - result[pair[0]] = pair[1] - } - } - } - } - - if integration != nil { - for _, c := range integration.Spec.Configuration { - if c.Type == configurationType { - pair := strings.Split(c.Value, "=") - if len(pair) == 2 { - result[pair[0]] = pair[1] - } - } - } - } - - return result -} - -// CombineConfigurationAsSlice -- -func CombineConfigurationAsSlice(configurationType string, context *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) []string { - result := make(map[string]bool, 0) - if context != nil { - // Add context properties first so integrations can - // override it - for _, c := range context.Spec.Configuration { - if c.Type == configurationType { - result[c.Value] = true - } - } - } - - for _, c := range integration.Spec.Configuration { - if c.Type == configurationType { - result[c.Value] = true - } - } - - keys := make([]string, 0, len(result)) - for k := range result { - keys = append(keys, k) - } - - return keys -} diff --git a/pkg/stub/action/integration/deploy.go b/pkg/trait/base.go similarity index 51% copy from pkg/stub/action/integration/deploy.go copy to pkg/trait/base.go index 8067b3c..69a46b9 100644 --- a/pkg/stub/action/integration/deploy.go +++ b/pkg/trait/base.go @@ -15,57 +15,38 @@ See the License for the specific language governing permissions and limitations under the License. */ -package integration +package trait import ( "fmt" - "github.com/sirupsen/logrus" + "github.com/apache/camel-k/pkg/util/kubernetes" "strings" - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/operator-framework/operator-sdk/pkg/sdk" - "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// NewDeployAction create an action that handles integration deploy -func NewDeployAction() Action { - return &deployAction{} +type baseTrait struct { } -type deployAction struct { +func (*baseTrait) ID() ID { + return ID("base") } -func (action *deployAction) Name() string { - return "deploy" -} - -func (action *deployAction) CanHandle(integration *v1alpha1.Integration) bool { - return integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying -} - -func (action *deployAction) Handle(integration *v1alpha1.Integration) error { - ctx, err := LookupContextForIntegration(integration) - if err != nil { - return err +func (d *baseTrait) Customize(environment Environment, resources *kubernetes.Collection) (bool, error) { + var cm *corev1.ConfigMap + var err error + if cm, err = d.getConfigMapFor(environment); err != nil { + return false, err } - err = createOrUpdateConfigMap(ctx, integration) - if err != nil { - return err + resources.Add(cm) + var depl *appsv1.Deployment + if depl, err = d.getDeploymentFor(environment); err != nil { + return false, err } - err = createOrUpdateDeployment(ctx, integration) - if err != nil { - return err - } - - target := integration.DeepCopy() - logrus.Info("Integration ", target.Name, " transitioning to state ", v1alpha1.IntegrationPhaseRunning) - target.Status.Phase = v1alpha1.IntegrationPhaseRunning - - return sdk.Update(target) + resources.Add(depl) + return true, nil } // ********************************** @@ -74,13 +55,10 @@ func (action *deployAction) Handle(integration *v1alpha1.Integration) error { // // ********************************** -func getConfigMapFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) (*corev1.ConfigMap, error) { - controller := true - blockOwnerDeletion := true - +func (*baseTrait) getConfigMapFor(e Environment) (*corev1.ConfigMap, error) { // combine properties of integration with context, integration // properties have the priority - properties := CombineConfigurationAsMap("property", ctx, integration) + properties := CombineConfigurationAsMap("property", e.Context, e.Integration) cm := corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ @@ -88,26 +66,16 @@ func getConfigMapFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Int APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: integration.Name, - Namespace: integration.Namespace, - Labels: integration.Labels, + Name: e.Integration.Name, + Namespace: e.Integration.Namespace, + Labels: e.Integration.Labels, Annotations: map[string]string{ - "camel.apache.org/source.language": string(integration.Spec.Source.Language), - "camel.apache.org/source.name": integration.Spec.Source.Name, - }, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: integration.APIVersion, - Kind: integration.Kind, - Name: integration.Name, - UID: integration.UID, - Controller: &controller, - BlockOwnerDeletion: &blockOwnerDeletion, - }, + "camel.apache.org/source.language": string(e.Integration.Spec.Source.Language), + "camel.apache.org/source.name": e.Integration.Spec.Source.Name, }, }, Data: map[string]string{ - "integration": integration.Spec.Source.Content, + "integration": e.Integration.Spec.Source.Content, "properties": PropertiesString(properties), }, } @@ -115,56 +83,37 @@ func getConfigMapFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Int return &cm, nil } -func createOrUpdateConfigMap(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) error { - cm, err := getConfigMapFor(ctx, integration) - if err != nil { - return err - } - - err = sdk.Create(cm) - if err != nil && k8serrors.IsAlreadyExists(err) { - err = sdk.Update(cm) - } - if err != nil { - return errors.Wrap(err, "could not create or replace configmap for integration "+integration.Name) - } - - return err -} - // ********************************** // // Deployment // // ********************************** -func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) (*appsv1.Deployment, error) { - controller := true - blockOwnerDeletion := true - sourceName := strings.TrimPrefix(integration.Spec.Source.Name, "/") +func (*baseTrait) getDeploymentFor(e Environment) (*appsv1.Deployment, error) { + sourceName := strings.TrimPrefix(e.Integration.Spec.Source.Name, "/") // combine environment of integration with context, integration // environment has the priority - environment := CombineConfigurationAsMap("env", ctx, integration) + environment := CombineConfigurationAsMap("env", e.Context, e.Integration) // set env vars needed by the runtime environment["JAVA_MAIN_CLASS"] = "org.apache.camel.k.jvm.Application" // camel-k runtime environment["CAMEL_K_ROUTES_URI"] = "file:/etc/camel/conf/" + sourceName - environment["CAMEL_K_ROUTES_LANGUAGE"] = string(integration.Spec.Source.Language) + environment["CAMEL_K_ROUTES_LANGUAGE"] = string(e.Integration.Spec.Source.Language) environment["CAMEL_K_CONF"] = "/etc/camel/conf/application.properties" environment["CAMEL_K_CONF_D"] = "/etc/camel/conf.d" // add a dummy env var to trigger deployment if everything but the code // has been changed - environment["CAMEL_K_DIGEST"] = integration.Status.Digest + environment["CAMEL_K_DIGEST"] = e.Integration.Status.Digest // optimizations environment["AB_JOLOKIA_OFF"] = "true" labels := map[string]string{ - "camel.apache.org/integration": integration.Name, + "camel.apache.org/integration": e.Integration.Name, } deployment := appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ @@ -172,23 +121,13 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In APIVersion: appsv1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ - Name: integration.Name, - Namespace: integration.Namespace, - Labels: integration.Labels, - Annotations: integration.Annotations, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: integration.APIVersion, - Kind: integration.Kind, - Name: integration.Name, - Controller: &controller, - BlockOwnerDeletion: &blockOwnerDeletion, - UID: integration.UID, - }, - }, + Name: e.Integration.Name, + Namespace: e.Integration.Namespace, + Labels: e.Integration.Labels, + Annotations: e.Integration.Annotations, }, Spec: appsv1.DeploymentSpec{ - Replicas: integration.Spec.Replicas, + Replicas: e.Integration.Spec.Replicas, Selector: &metav1.LabelSelector{ MatchLabels: labels, }, @@ -199,8 +138,8 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: integration.Name, - Image: integration.Status.Image, + Name: e.Integration.Name, + Image: e.Integration.Status.Image, Env: EnvironmentAsEnvVarSlice(environment), }, }, @@ -226,7 +165,7 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: integration.Name, + Name: e.Integration.Name, }, Items: []corev1.KeyToPath{ { @@ -250,7 +189,7 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In // Volumes :: Additional ConfigMaps // - cmList := CombineConfigurationAsSlice("configmap", ctx, integration) + cmList := CombineConfigurationAsSlice("configmap", e.Context, e.Integration) for _, cmName := range cmList { cnt++ @@ -275,7 +214,7 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In // Volumes :: Additional Secrets // - secretList := CombineConfigurationAsSlice("secret", ctx, integration) + secretList := CombineConfigurationAsSlice("secret", e.Context, e.Integration) for _, secretName := range secretList { cnt++ @@ -303,20 +242,3 @@ func getDeploymentFor(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.In return &deployment, nil } - -func createOrUpdateDeployment(ctx *v1alpha1.IntegrationContext, integration *v1alpha1.Integration) error { - deployment, err := getDeploymentFor(ctx, integration) - if err != nil { - return err - } - - err = sdk.Create(deployment) - if err != nil && k8serrors.IsAlreadyExists(err) { - err = sdk.Update(deployment) - } - if err != nil { - return errors.Wrap(err, "could not create or replace deployment for integration "+integration.Name) - } - - return nil -} diff --git a/pkg/trait/catalog/catalog.go b/pkg/trait/catalog.go similarity index 58% rename from pkg/trait/catalog/catalog.go rename to pkg/trait/catalog.go index 924ed37..3325860 100644 --- a/pkg/trait/catalog/catalog.go +++ b/pkg/trait/catalog.go @@ -15,39 +15,48 @@ See the License for the specific language governing permissions and limitations under the License. */ -package catalog +package trait import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/apache/camel-k/pkg/trait" "github.com/apache/camel-k/pkg/util/kubernetes" ) -// TraitID uniquely identifies a trait -type TraitID string - -const ( - // Expose exposes a integration to the external world - Expose TraitID = "expose" +var ( + tExpose = &exposeTrait{} + tBase = &baseTrait{} + tOwner = &ownerTrait{} ) -// A Catalog is just a DeploymentCustomizer that applies multiple traits -type Catalog trait.DeploymentCustomizer - -// For returns a Catalog for the given integration details -func For(environment trait.Environment) Catalog { - +// CustomizersFor returns a Catalog for the given integration details +func CustomizersFor(environment Environment) Customizer { + switch environment.Platform.Spec.Cluster { + case v1alpha1.IntegrationPlatformClusterOpenShift: + return compose( + tBase, + tExpose, + tOwner, + ) + case v1alpha1.IntegrationPlatformClusterKubernetes: + return compose( + tBase, + tExpose, + tOwner, + ) + // case Knative: ... + } + return nil } -func compose(traits ...trait.DeploymentCustomizer) trait.DeploymentCustomizer { +func compose(traits ...Customizer) Customizer { if len(traits) == 0 { return &identityTrait{} } else if len(traits) == 1 { return traits[0] } - var composite trait.DeploymentCustomizer = &identityTrait{} + var composite Customizer = &identityTrait{} for _, t := range traits { - composite = &catalogCustomizer{ + composite = &chainedCustomizer{ t1: composite, t2: t, } @@ -57,29 +66,28 @@ func compose(traits ...trait.DeploymentCustomizer) trait.DeploymentCustomizer { // ------------------------------------------- -type catalogCustomizer struct { - t1 trait.DeploymentCustomizer - t2 trait.DeploymentCustomizer +type chainedCustomizer struct { + t1 Customizer + t2 Customizer } -func (c *catalogCustomizer) Name() string { - return "" +func (c *chainedCustomizer) ID() ID { + return ID("") } -func (c *catalogCustomizer) Customize(environment trait.Environment, resources *kubernetes.Collection) (bool, error) { +func (c *chainedCustomizer) Customize(environment Environment, resources *kubernetes.Collection) (bool, error) { atLeastOnce := false var done bool var err error if done, err = c.t1.Customize(environment, resources); err != nil { return false, err - } else if done && c.t1.Name() != "" { - environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, c.t1.Name()) + } else if done && c.t1.ID() != "" { + environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, c.t1.ID()) } atLeastOnce = atLeastOnce || done done2, err := c.t2.Customize(environment, resources) - if done2 && c.t2.Name() != "" { - environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, c.t2.Name()) + if done2 && c.t2.ID() != "" { + environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, c.t2.ID()) } - environment.ExecutedCustomizers = append(environment.ExecutedCustomizers, c.t1.Name()) return atLeastOnce || done2, err } diff --git a/pkg/trait/catalog/expose.go b/pkg/trait/expose.go similarity index 80% rename from pkg/trait/catalog/expose.go rename to pkg/trait/expose.go index 91890eb..2dac116 100644 --- a/pkg/trait/catalog/expose.go +++ b/pkg/trait/expose.go @@ -15,20 +15,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package catalog +package trait import ( - "github.com/apache/camel-k/pkg/trait" "github.com/apache/camel-k/pkg/util/kubernetes" ) type exposeTrait struct { } -func (*exposeTrait) Name() string { - return "expose" +func (*exposeTrait) ID() ID { + return ID("expose") } -func (*exposeTrait) Customize(environment trait.Environment, resources *kubernetes.Collection) (bool, error) { +func (*exposeTrait) Customize(environment Environment, resources *kubernetes.Collection) (bool, error) { return false, nil } diff --git a/pkg/trait/catalog/identity.go b/pkg/trait/identity.go similarity index 79% rename from pkg/trait/catalog/identity.go rename to pkg/trait/identity.go index cddd0f4..eda9d3a 100644 --- a/pkg/trait/catalog/identity.go +++ b/pkg/trait/identity.go @@ -15,20 +15,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -package catalog +package trait import ( - "github.com/apache/camel-k/pkg/trait" "github.com/apache/camel-k/pkg/util/kubernetes" ) type identityTrait struct { } -func (*identityTrait) Name() string { - return "identity" +func (*identityTrait) ID() ID { + return ID("identity") } -func (*identityTrait) Customize(environment trait.Environment, resources *kubernetes.Collection) (bool, error) { +func (*identityTrait) Customize(environment Environment, resources *kubernetes.Collection) (bool, error) { return false, nil } diff --git a/pkg/trait/owner.go b/pkg/trait/owner.go new file mode 100644 index 0000000..905800b --- /dev/null +++ b/pkg/trait/owner.go @@ -0,0 +1,48 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trait + +import "github.com/apache/camel-k/pkg/util/kubernetes" +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// ownerTrait ensures that all created resources belong to the integration being created +type ownerTrait struct { +} + +func (*ownerTrait) ID() ID { + return ID("identity") +} + +func (*ownerTrait) Customize(e Environment, resources *kubernetes.Collection) (bool, error) { + controller := true + blockOwnerDeletion := true + resources.VisitMetaObject(func(res metav1.Object) { + references := []metav1.OwnerReference{ + { + APIVersion: e.Integration.APIVersion, + Kind: e.Integration.Kind, + Name: e.Integration.Name, + UID: e.Integration.UID, + Controller: &controller, + BlockOwnerDeletion: &blockOwnerDeletion, + }, + } + res.SetOwnerReferences(references) + }) + return true, nil +} diff --git a/pkg/trait/trait.go b/pkg/trait/trait.go index e0bb7ef..90e4d29 100644 --- a/pkg/trait/trait.go +++ b/pkg/trait/trait.go @@ -19,6 +19,8 @@ package trait import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/discover" + "github.com/apache/camel-k/pkg/platform" "github.com/apache/camel-k/pkg/util/kubernetes" ) @@ -27,13 +29,38 @@ type Environment struct { Platform *v1alpha1.IntegrationPlatform Context *v1alpha1.IntegrationContext Integration *v1alpha1.Integration - ExecutedCustomizers []string + Dependencies []string + ExecutedCustomizers []ID } -// A DeploymentCustomizer performs customization of the deployed objects -type DeploymentCustomizer interface { +// NewEnvironment creates a Environment from the given data +func NewEnvironment(integration *v1alpha1.Integration) (*Environment, error) { + pl, err := platform.GetCurrentPlatform(integration.Namespace) + if err != nil { + return nil, err + } + ctx, err := GetIntegrationContext(integration) + if err != nil { + return nil, err + } + dependencies := discover.Dependencies(integration.Spec.Source) + + return &Environment{ + Platform: pl, + Context: ctx, + Integration: integration, + Dependencies: dependencies, + ExecutedCustomizers: make([]ID, 0), + }, nil +} + +// ID uniquely identifies a trait +type ID string + +// A Customizer performs customization of the deployed objects +type Customizer interface { // The Name of the customizer - Name() string + ID() ID // Customize executes the trait customization on the resources and return true if the resources have been changed Customize(environment Environment, resources *kubernetes.Collection) (bool, error) } diff --git a/pkg/stub/action/integration/util.go b/pkg/trait/util.go similarity index 73% copy from pkg/stub/action/integration/util.go copy to pkg/trait/util.go index ab9cf79..0e5da55 100644 --- a/pkg/stub/action/integration/util.go +++ b/pkg/trait/util.go @@ -15,55 +15,27 @@ See the License for the specific language governing permissions and limitations under the License. */ -package integration +package trait import ( "fmt" - "strings" - - "github.com/apache/camel-k/pkg/util" - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/operator-framework/operator-sdk/pkg/sdk" - "k8s.io/api/core/v1" - "github.com/pkg/errors" + "k8s.io/api/core/v1" + "strings" ) -// LookupContextForIntegration -- -func LookupContextForIntegration(integration *v1alpha1.Integration) (*v1alpha1.IntegrationContext, error) { - if integration.Spec.Context != "" { - name := integration.Spec.Context - ctx := v1alpha1.NewIntegrationContext(integration.Namespace, name) - - if err := sdk.Get(&ctx); err != nil { - return nil, errors.Wrapf(err, "unable to find integration context %s, %s", ctx.Name, err) - } - - return &ctx, nil - } - - ctxList := v1alpha1.NewIntegrationContextList() - if err := sdk.List(integration.Namespace, &ctxList); err != nil { - return nil, err - } - - for _, ctx := range ctxList.Items { - if ctx.Labels["camel.apache.org/context.type"] == "platform" { - ideps := len(integration.Spec.Dependencies) - cdeps := len(ctx.Spec.Dependencies) - - if ideps != cdeps { - continue - } - - if util.StringSliceContains(ctx.Spec.Dependencies, integration.Spec.Dependencies) { - return &ctx, nil - } - } +// GetIntegrationContext retrieves the context set on the integration +func GetIntegrationContext(integration *v1alpha1.Integration) (*v1alpha1.IntegrationContext, error) { + if integration.Spec.Context == "" { + return nil, errors.New("no context set on the integration") } - return nil, nil + name := integration.Spec.Context + ctx := v1alpha1.NewIntegrationContext(integration.Namespace, name) + err := sdk.Get(&ctx) + return &ctx, err } // PropertiesString -- diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go index a3ff2b7..91cb7ee 100644 --- a/pkg/util/kubernetes/collection.go +++ b/pkg/util/kubernetes/collection.go @@ -20,6 +20,7 @@ package kubernetes import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -28,6 +29,13 @@ type Collection struct { items []runtime.Object } +// NewCollection creates a new empty collection +func NewCollection() *Collection { + return &Collection{ + items: make([]runtime.Object, 0), + } +} + // Items returns all resources belonging to the collection func (c *Collection) Items() []runtime.Object { return c.items @@ -48,7 +56,7 @@ func (c *Collection) VisitDeployment(visitor func(*appsv1.Deployment)) { } // VisitConfigMap executes the visitor function on all ConfigMap resources -func (c *Collection) VisitConfigMap(visitor func(configMap *corev1.ConfigMap)) { +func (c *Collection) VisitConfigMap(visitor func(*corev1.ConfigMap)) { c.Visit(func(res runtime.Object) { if conv, ok := res.(*corev1.ConfigMap); ok { visitor(conv) @@ -56,6 +64,15 @@ func (c *Collection) VisitConfigMap(visitor func(configMap *corev1.ConfigMap)) { }) } +// VisitMetaObject executes the visitor function on all meta.Object resources +func (c *Collection) VisitMetaObject(visitor func(metav1.Object)) { + c.Visit(func(res runtime.Object) { + if conv, ok := res.(metav1.Object); ok { + visitor(conv) + } + }) +} + // Visit executes the visitor function on all resources func (c *Collection) Visit(visitor func(runtime.Object)) { for _, res := range c.items {