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{})
