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

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

commit 030f958045c43f8c848503bd45f6c2627dbb2fc1
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Fri Jun 17 16:17:19 2022 +0200

    feat(cli): promote kameletbinding support
---
 e2e/common/cli/promote_test.go | 28 ++++++++++--
 pkg/cmd/promote.go             | 98 +++++++++++++++++++++++++++++++++++-------
 2 files changed, 107 insertions(+), 19 deletions(-)

diff --git a/e2e/common/cli/promote_test.go b/e2e/common/cli/promote_test.go
index 3f7840165..c2b7182c7 100644
--- a/e2e/common/cli/promote_test.go
+++ b/e2e/common/cli/promote_test.go
@@ -46,7 +46,7 @@ func TestKamelCLIPromote(t *testing.T) {
                secData["my-secret-key"] = "very top secret development"
                NewPlainTextSecret(nsDev, "my-sec", secData)
 
-               t.Run("plain integration", func(t *testing.T) {
+               t.Run("plain integration dev", func(t *testing.T) {
                        Expect(Kamel("run", "-n", nsDev, 
"./files/promote-route.groovy",
                                "--config", "configmap:my-cm",
                                "--config", "secret:my-sec",
@@ -57,13 +57,20 @@ func TestKamelCLIPromote(t *testing.T) {
                        Eventually(IntegrationLogs(nsDev, "promote-route"), 
TestTimeoutShort).Should(ContainSubstring("very top secret development"))
                })
 
-               t.Run("kamelet integration", func(t *testing.T) {
+               t.Run("kamelet integration dev", func(t *testing.T) {
                        Expect(CreateTimerKamelet(nsDev, 
"my-own-timer-source")()).To(Succeed())
                        Expect(Kamel("run", "-n", nsDev, 
"files/timer-kamelet-usage.groovy").Execute()).To(Succeed())
                        Eventually(IntegrationPodPhase(nsDev, 
"timer-kamelet-usage"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
                        Eventually(IntegrationLogs(nsDev, 
"timer-kamelet-usage"), TestTimeoutShort).Should(ContainSubstring("Hello 
world"))
                })
 
+               t.Run("kamelet binding dev", func(t *testing.T) {
+                       Expect(CreateTimerKamelet(nsDev, 
"kb-timer-source")()).To(Succeed())
+                       Expect(Kamel("bind", "kb-timer-source", "log:info", 
"-p", "message=my-kamelet-binding-rocks", "-n", nsDev).Execute()).To(Succeed())
+                       Eventually(IntegrationPodPhase(nsDev, 
"kb-timer-source-to-log"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
+                       Eventually(IntegrationLogs(nsDev, 
"kb-timer-source-to-log"), 
TestTimeoutShort).Should(ContainSubstring("my-kamelet-binding-rocks"))
+               })
+
                // Prod environment namespace
                WithNewTestNamespace(t, func(nsProd string) {
                        Expect(Kamel("install", "-n", 
nsProd).Execute()).To(Succeed())
@@ -85,7 +92,7 @@ func TestKamelCLIPromote(t *testing.T) {
                        secData["my-secret-key"] = "very top secret production"
                        NewPlainTextSecret(nsProd, "my-sec", secData)
 
-                       t.Run("Production integration", func(t *testing.T) {
+                       t.Run("plain integration promotion", func(t *testing.T) 
{
                                Expect(Kamel("promote", "-n", nsDev, 
"promote-route", "--to", nsProd).Execute()).To(Succeed())
                                Eventually(IntegrationPodPhase(nsProd, 
"promote-route"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
                                Eventually(IntegrationConditionStatus(nsProd, 
"promote-route", v1.IntegrationConditionReady), 
TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
@@ -99,7 +106,7 @@ func TestKamelCLIPromote(t *testing.T) {
                                Expect(Kamel("promote", "-n", nsDev, 
"timer-kamelet-usage", "--to", nsProd).Execute()).NotTo(Succeed())
                        })
 
-                       t.Run("kamelet integration", func(t *testing.T) {
+                       t.Run("kamelet integration promotion", func(t 
*testing.T) {
                                Expect(CreateTimerKamelet(nsProd, 
"my-own-timer-source")()).To(Succeed())
                                Expect(Kamel("promote", "-n", nsDev, 
"timer-kamelet-usage", "--to", nsProd).Execute()).To(Succeed())
                                Eventually(IntegrationPodPhase(nsProd, 
"timer-kamelet-usage"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
@@ -107,6 +114,19 @@ func TestKamelCLIPromote(t *testing.T) {
                                // They must use the same image
                                Expect(IntegrationPodImage(nsProd, 
"timer-kamelet-usage")()).Should(Equal(IntegrationPodImage(nsDev, 
"timer-kamelet-usage")()))
                        })
+
+                       t.Run("no kamelet for kameletbinding in destination", 
func(t *testing.T) {
+                               Expect(Kamel("promote", "-n", nsDev, 
"kb-timer-source", "--to", nsProd).Execute()).NotTo(Succeed())
+                       })
+
+                       t.Run("kamelet binding promotion", func(t *testing.T) {
+                               Expect(CreateTimerKamelet(nsProd, 
"kb-timer-source")()).To(Succeed())
+                               Expect(Kamel("promote", "-n", nsDev, 
"kb-timer-source-to-log", "--to", nsProd).Execute()).To(Succeed())
+                               Eventually(IntegrationPodPhase(nsProd, 
"kb-timer-source-to-log"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
+                               Eventually(IntegrationLogs(nsProd, 
"kb-timer-source-to-log"), 
TestTimeoutShort).Should(ContainSubstring("my-kamelet-binding-rocks"))
+                               // They must use the same image
+                               Expect(IntegrationPodImage(nsProd, 
"kb-timer-source-to-log")()).Should(Equal(IntegrationPodImage(nsDev, 
"kb-timer-source-to-log")()))
+                       })
                })
        })
 }
diff --git a/pkg/cmd/promote.go b/pkg/cmd/promote.go
index 28be8b28b..6e15c6cc3 100644
--- a/pkg/cmd/promote.go
+++ b/pkg/cmd/promote.go
@@ -20,10 +20,11 @@ package cmd
 import (
        "context"
        "encoding/json"
-       "errors"
        "fmt"
        "strings"
 
+       "github.com/pkg/errors"
+
        v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
        "github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
        "github.com/apache/camel-k/pkg/client"
@@ -33,6 +34,7 @@ import (
        "github.com/apache/camel-k/pkg/util/resource"
        "github.com/spf13/cobra"
        corev1 "k8s.io/api/core/v1"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
        k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -43,8 +45,8 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *promoteCmdO
        }
        cmd := cobra.Command{
                Use:     "promote integration --to [namespace] ...",
-               Short:   "Promote an Integration from an environment to 
another",
-               Long:    "Promote an Integration from an environment to 
another, for example from a Development environment to a Production 
environment",
+               Short:   "Promote an Integration/KameletBinding from an 
environment to another",
+               Long:    "Promote an Integration/KameletBinding from an 
environment to another, for example from a Development environment to a 
Production environment",
                PreRunE: decode(&options),
                RunE:    options.run,
        }
@@ -61,7 +63,7 @@ type promoteCmdOptions struct {
 
 func (o *promoteCmdOptions) validate(_ *cobra.Command, args []string) error {
        if len(args) != 1 {
-               return errors.New("promote expects an Integration name 
argument")
+               return errors.New("promote expects an 
Integration/KameletBinding name argument")
        }
        if o.To == "" {
                return errors.New("promote expects a destination namespace as 
--to argument")
@@ -74,39 +76,61 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args 
[]string) error {
                return err
        }
 
-       it := args[0]
+       name := args[0]
        c, err := o.GetCmdClient()
        if err != nil {
-               return err
+               return errors.Wrap(err, "could not retrieve cluster client")
        }
 
        opSource, err := operatorInfo(o.Context, c, o.Namespace)
        if err != nil {
-               return fmt.Errorf("could not retrieve info for Camel K operator 
source")
+               return errors.Wrap(err, "could not retrieve info for Camel K 
operator source")
        }
        opDest, err := operatorInfo(o.Context, c, o.To)
        if err != nil {
-               return fmt.Errorf("could not retrieve info for Camel K operator 
source")
+               return errors.Wrap(err, "could not retrieve info for Camel K 
operator destination")
        }
 
        err = checkOpsCompatibility(cmd, opSource, opDest)
        if err != nil {
-               return err
+               return errors.Wrap(err, "could not verify operators 
compatibility")
+       }
+       promoteKameletBinding := false
+       var sourceIntegration *v1.Integration
+       // We first look if a KameletBinding with the name exists
+       sourceKameletBinding, err := o.getKameletBinding(c, name)
+       if err != nil && !k8serrors.IsNotFound(err) {
+               return errors.Wrap(err, "problems looking for KameletBinding 
"+name)
+       }
+       if sourceKameletBinding != nil {
+               promoteKameletBinding = true
        }
-       sourceIntegration, err := o.getIntegration(c, it)
+       sourceIntegration, err = o.getIntegration(c, name)
        if err != nil {
-               return err
+               return errors.Wrap(err, "could not get Integration "+name)
        }
        if sourceIntegration.Status.Phase != v1.IntegrationPhaseRunning {
-               return fmt.Errorf("could not promote an integration in %s 
status", sourceIntegration.Status.Phase)
+               return fmt.Errorf("could not promote an Integration in %s 
status", sourceIntegration.Status.Phase)
        }
        err = o.validateDestResources(c, sourceIntegration)
        if err != nil {
-               return err
+               return errors.Wrap(err, "could not validate destination 
resources")
+       }
+       if promoteKameletBinding {
+               // KameletBinding promotion
+               destKameletBinding, err := 
o.editKameletBinding(sourceKameletBinding, sourceIntegration)
+               if err != nil {
+                       return errors.Wrap(err, "could not edit KameletBinding 
"+name)
+               }
+
+               return c.Create(o.Context, destKameletBinding)
        }
+       // Plain Integration promotion
        destIntegration, err := o.editIntegration(sourceIntegration)
        if err != nil {
-               return err
+               if err != nil {
+                       return errors.Wrap(err, "could not edit Integration 
"+name)
+               }
        }
 
        return c.Create(o.Context, destIntegration)
@@ -126,6 +150,19 @@ func checkOpsCompatibility(cmd *cobra.Command, source, 
dest map[string]string) e
        return nil
 }
 
+func (o *promoteCmdOptions) getKameletBinding(c client.Client, name string) 
(*v1alpha1.KameletBinding, error) {
+       it := v1alpha1.NewKameletBinding(o.Namespace, name)
+       key := k8sclient.ObjectKey{
+               Name:      name,
+               Namespace: o.Namespace,
+       }
+       if err := c.Get(o.Context, key, &it); err != nil {
+               return nil, err
+       }
+
+       return &it, nil
+}
+
 func (o *promoteCmdOptions) getIntegration(c client.Client, name string) 
(*v1.Integration, error) {
        it := v1.NewIntegration(o.Namespace, name)
        key := k8sclient.ObjectKey{
@@ -133,7 +170,7 @@ func (o *promoteCmdOptions) getIntegration(c client.Client, 
name string) (*v1.In
                Namespace: o.Namespace,
        }
        if err := c.Get(o.Context, key, &it); err != nil {
-               return nil, fmt.Errorf("could not find integration %s in 
namespace %s", it.Name, o.Namespace)
+               return nil, err
        }
 
        return &it, nil
@@ -145,6 +182,9 @@ func (o *promoteCmdOptions) validateDestResources(c 
client.Client, it *v1.Integr
        var secrets []string
        var pvcs []string
        var kamelets []string
+       if it.Spec.Traits == nil {
+               return nil
+       }
        // Mount trait
        mounts := it.Spec.Traits["mount"]
        if err := json.Unmarshal(mounts.Configuration.RawMessage, &traits); err 
!= nil {
@@ -332,6 +372,34 @@ func (o *promoteCmdOptions) editIntegration(it 
*v1.Integration) (*v1.Integration
        return &dst, err
 }
 
+func (o *promoteCmdOptions) editKameletBinding(kb *v1alpha1.KameletBinding, it 
*v1.Integration) (*v1alpha1.KameletBinding, error) {
+       dst := v1alpha1.NewKameletBinding(o.To, kb.Name)
+       dst.Spec = *kb.Spec.DeepCopy()
+       contImage := it.Status.Image
+       if dst.Spec.Integration == nil {
+               dst.Spec.Integration = &v1.IntegrationSpec{}
+       }
+       if dst.Spec.Integration.Traits == nil {
+               dst.Spec.Integration.Traits = map[string]v1.TraitSpec{}
+       }
+       editedContTrait, err := 
editContainerImage(dst.Spec.Integration.Traits["container"], contImage)
+       dst.Spec.Integration.Traits["container"] = editedContTrait
+       if dst.Spec.Source.Ref != nil {
+               dst.Spec.Source.Ref.Namespace = o.To
+       }
+       if dst.Spec.Sink.Ref != nil {
+               dst.Spec.Sink.Ref.Namespace = o.To
+       }
+       if dst.Spec.Steps != nil {
+               for _, step := range dst.Spec.Steps {
+                       if step.Ref != nil {
+                               step.Ref.Namespace = o.To
+                       }
+               }
+       }
+       return &dst, err
+}
+
 func editContainerImage(contTrait v1.TraitSpec, image string) (v1.TraitSpec, 
error) {
        var editedTrait v1.TraitSpec
        m := make(map[string]map[string]interface{})

Reply via email to