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 0cad7974633a54cef58e7f0e0cf9d000d6787d1c
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Wed Jun 15 09:57:47 2022 +0200

    feat(e2e): promote integration test
---
 e2e/common/cli/files/promote-route.groovy |  25 +++++++
 e2e/common/cli/promote_test.go            | 112 ++++++++++++++++++++++++++++
 pkg/cmd/promote.go                        | 119 ++++++++++++++++++++----------
 pkg/cmd/promote_test.go                   |  53 +++++++------
 4 files changed, 249 insertions(+), 60 deletions(-)

diff --git a/e2e/common/cli/files/promote-route.groovy 
b/e2e/common/cli/files/promote-route.groovy
new file mode 100644
index 000000000..943a4ab91
--- /dev/null
+++ b/e2e/common/cli/files/promote-route.groovy
@@ -0,0 +1,25 @@
+// camel-k: language=groovy
+/*
+ * 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.
+ */
+
+from('timer:configmap')
+    .setBody()
+        .simple("resource:classpath:my-configmap-key")
+    .log('configmap: ${body}')
+    .setBody()
+        .simple("resource:classpath:my-secret-key")
+    .log('secret: ${body}')
\ No newline at end of file
diff --git a/e2e/common/cli/promote_test.go b/e2e/common/cli/promote_test.go
new file mode 100644
index 000000000..3f7840165
--- /dev/null
+++ b/e2e/common/cli/promote_test.go
@@ -0,0 +1,112 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> 
Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 common
+
+import (
+       "testing"
+
+       corev1 "k8s.io/api/core/v1"
+
+       . "github.com/onsi/gomega"
+
+       . "github.com/apache/camel-k/e2e/support"
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+)
+
+func TestKamelCLIPromote(t *testing.T) {
+       // Dev environment namespace
+       WithNewTestNamespace(t, func(nsDev string) {
+               Expect(Kamel("install", "-n", nsDev).Execute()).To(Succeed())
+               // Dev content configmap
+               var cmData = make(map[string]string)
+               cmData["my-configmap-key"] = "I am development configmap!"
+               NewPlainTextConfigmap(nsDev, "my-cm", cmData)
+               // Dev secret
+               var secData = make(map[string]string)
+               secData["my-secret-key"] = "very top secret development"
+               NewPlainTextSecret(nsDev, "my-sec", secData)
+
+               t.Run("plain integration", func(t *testing.T) {
+                       Expect(Kamel("run", "-n", nsDev, 
"./files/promote-route.groovy",
+                               "--config", "configmap:my-cm",
+                               "--config", "secret:my-sec",
+                       ).Execute()).To(Succeed())
+                       Eventually(IntegrationPodPhase(nsDev, "promote-route"), 
TestTimeoutMedium).Should(Equal(corev1.PodRunning))
+                       Eventually(IntegrationConditionStatus(nsDev, 
"promote-route", v1.IntegrationConditionReady), 
TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+                       Eventually(IntegrationLogs(nsDev, "promote-route"), 
TestTimeoutShort).Should(ContainSubstring("I am development configmap!"))
+                       Eventually(IntegrationLogs(nsDev, "promote-route"), 
TestTimeoutShort).Should(ContainSubstring("very top secret development"))
+               })
+
+               t.Run("kamelet integration", 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"))
+               })
+
+               // Prod environment namespace
+               WithNewTestNamespace(t, func(nsProd string) {
+                       Expect(Kamel("install", "-n", 
nsProd).Execute()).To(Succeed())
+
+                       t.Run("no configmap in destination", func(t *testing.T) 
{
+                               Expect(Kamel("promote", "-n", nsDev, 
"promote-route", "--to", nsProd).Execute()).NotTo(Succeed())
+                       })
+                       // Prod content configmap
+                       var cmData = make(map[string]string)
+                       cmData["my-configmap-key"] = "I am production!"
+                       NewPlainTextConfigmap(nsProd, "my-cm", cmData)
+
+                       t.Run("no secret in destination", func(t *testing.T) {
+                               Expect(Kamel("promote", "-n", nsDev, 
"promote-route", "--to", nsProd).Execute()).NotTo(Succeed())
+                       })
+
+                       // Prod secret
+                       var secData = make(map[string]string)
+                       secData["my-secret-key"] = "very top secret production"
+                       NewPlainTextSecret(nsProd, "my-sec", secData)
+
+                       t.Run("Production integration", 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))
+                               Eventually(IntegrationLogs(nsProd, 
"promote-route"), TestTimeoutShort).Should(ContainSubstring("I am production!"))
+                               Eventually(IntegrationLogs(nsProd, 
"promote-route"), TestTimeoutShort).Should(ContainSubstring("very top secret 
production"))
+                               // They must use the same image
+                               Expect(IntegrationPodImage(nsProd, 
"promote-route")()).Should(Equal(IntegrationPodImage(nsDev, "promote-route")()))
+                       })
+
+                       t.Run("no kamelet in destination", func(t *testing.T) {
+                               Expect(Kamel("promote", "-n", nsDev, 
"timer-kamelet-usage", "--to", nsProd).Execute()).NotTo(Succeed())
+                       })
+
+                       t.Run("kamelet integration", 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))
+                               Eventually(IntegrationLogs(nsProd, 
"timer-kamelet-usage"), TestTimeoutShort).Should(ContainSubstring("Hello 
world"))
+                               // They must use the same image
+                               Expect(IntegrationPodImage(nsProd, 
"timer-kamelet-usage")()).Should(Equal(IntegrationPodImage(nsDev, 
"timer-kamelet-usage")()))
+                       })
+               })
+       })
+}
diff --git a/pkg/cmd/promote.go b/pkg/cmd/promote.go
index 5c65b82f2..b24eafd80 100644
--- a/pkg/cmd/promote.go
+++ b/pkg/cmd/promote.go
@@ -22,7 +22,6 @@ import (
        "encoding/json"
        "errors"
        "fmt"
-       "os"
        "strings"
 
        v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -44,16 +43,14 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *promoteCmdO
                RootCmdOptions: rootCmdOptions,
        }
        cmd := cobra.Command{
-               Use:     "promote integration -to [namespace] ...",
+               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",
-               Aliases: []string{"cp", "mv"},
-               Args:    options.validate,
                PreRunE: decode(&options),
                RunE:    options.run,
        }
 
-       cmd.Flags().StringP("to", "", "", "The namespace where to promote the 
Integration")
+       cmd.Flags().String("to", "", "The namespace where to promote the 
Integration")
 
        return &cmd, &options
 }
@@ -65,13 +62,19 @@ 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 name 
argument")
+       }
+       if o.To == "" {
+               return errors.New("promote expects a destination namespace as 
--to argument")
        }
-
        return nil
 }
 
 func (o *promoteCmdOptions) run(cmd *cobra.Command, args []string) error {
+       if err := o.validate(cmd, args); err != nil {
+               return err
+       }
+
        it := args[0]
        c, err := o.GetCmdClient()
        if err != nil {
@@ -87,26 +90,41 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args 
[]string) error {
                return fmt.Errorf("could not retrieve info for Camel K operator 
source")
        }
 
-       checkOpsCompatibility(cmd, opSource, opDest)
-
+       err = checkOpsCompatibility(cmd, opSource, opDest)
+       if err != nil {
+               return err
+       }
        sourceIntegration, err := o.getIntegration(c, it)
-       o.validateDestResources(c, sourceIntegration)
-       //destIntegration := o.editIntegration(sourceIntegration)
+       if err != nil {
+               return err
+       }
+       if sourceIntegration.Status.Phase != v1.IntegrationPhaseRunning {
+               return fmt.Errorf("could not promote an integration in %s 
status", sourceIntegration.Status.Phase)
+       }
+       err = o.validateDestResources(c, sourceIntegration)
+       if err != nil {
+               return err
+       }
+       destIntegration, err := o.editIntegration(sourceIntegration)
+       if err != nil {
+               return err
+       }
 
-       //return c.Create(o.Context, destIntegration)
-       return nil
+       return c.Create(o.Context, destIntegration)
 }
 
-func checkOpsCompatibility(cmd *cobra.Command, source, dest map[string]string) 
{
+func checkOpsCompatibility(cmd *cobra.Command, source, dest map[string]string) 
error {
        if !compatibleVersions(source["Version"], dest["Version"], cmd) {
-               panic(fmt.Sprintf("source (%s) and destination (%s) Camel K 
operator versions are not compatible", source["version"], dest["version"]))
+               return fmt.Errorf("source (%s) and destination (%s) Camel K 
operator versions are not compatible", source["Version"], dest["Version"])
        }
        if !compatibleVersions(source["Runtime Version"], dest["Runtime 
Version"], cmd) {
-               panic(fmt.Sprintf("source (%s) and destination (%s) Camel K 
runtime versions are not compatible", source["runtime version"], dest["runtime 
version"]))
+               return fmt.Errorf("source (%s) and destination (%s) Camel K 
runtime versions are not compatible", source["Runtime Version"], dest["Runtime 
Version"])
        }
        if source["Registry Address"] != source["Registry Address"] {
-               panic(fmt.Sprintf("source (%s) and destination (%s) Camel K 
container images registries are not the same", source["registry address"], 
dest["registry address"]))
+               return fmt.Errorf("source (%s) and destination (%s) Camel K 
container images registries are not the same", source["Registry Address"], 
dest["Registry Address"])
        }
+
+       return nil
 }
 
 func (o *promoteCmdOptions) getIntegration(c client.Client, name string) 
(*v1.Integration, error) {
@@ -122,7 +140,7 @@ func (o *promoteCmdOptions) getIntegration(c client.Client, 
name string) (*v1.In
        return &it, nil
 }
 
-func (o *promoteCmdOptions) validateDestResources(c client.Client, it 
*v1.Integration) {
+func (o *promoteCmdOptions) validateDestResources(c client.Client, it 
*v1.Integration) error {
        var traits map[string][]string
        var configmaps []string
        var secrets []string
@@ -162,34 +180,37 @@ func (o *promoteCmdOptions) validateDestResources(c 
client.Client, it *v1.Integr
        kamelets = o.listKamelets(c, it)
 
        anyError := false
+       var errorTrace string
        for _, name := range configmaps {
                if !existsCm(o.Context, c, name, o.To) {
                        anyError = true
-                       fmt.Printf("Configmap %s is missing from %s 
namespace\n", name, o.To)
+                       errorTrace += fmt.Sprintf("Configmap %s is missing from 
%s namespace\n", name, o.To)
                }
        }
        for _, name := range secrets {
                if !existsSecret(o.Context, c, name, o.To) {
                        anyError = true
-                       fmt.Printf("Secret %s is missing from %s namespace\n", 
name, o.To)
+                       errorTrace += fmt.Sprintf("Secret %s is missing from %s 
namespace\n", name, o.To)
                }
        }
        for _, name := range pvcs {
                if !existsPv(o.Context, c, name, o.To) {
                        anyError = true
-                       fmt.Printf("PersistentVolume %s is missing from %s 
namespace\n", name, o.To)
+                       errorTrace += fmt.Sprintf("PersistentVolume %s is 
missing from %s namespace\n", name, o.To)
                }
        }
        for _, name := range kamelets {
                if !existsKamelet(o.Context, c, name, o.To) {
                        anyError = true
-                       fmt.Printf("Kamelet %s is missing from %s namespace\n", 
name, o.To)
+                       errorTrace += fmt.Sprintf("Kamelet %s is missing from 
%s namespace\n", name, o.To)
                }
        }
 
        if anyError {
-               os.Exit(1)
+               return fmt.Errorf(errorTrace)
        }
+
+       return nil
 }
 
 func (o *promoteCmdOptions) listKamelets(c client.Client, it *v1.Integration) 
[]string {
@@ -211,7 +232,15 @@ func (o *promoteCmdOptions) listKamelets(c client.Client, 
it *v1.Integration) []
                }
        }
 
-       return kamelets
+       // We must remove any default source/sink
+       var filtered []string
+       for _, k := range kamelets {
+               if k != "source" && k != "sink" {
+                       filtered = append(filtered, k)
+               }
+       }
+
+       return filtered
 }
 
 func existsCm(ctx context.Context, c client.Client, name string, namespace 
string) bool {
@@ -266,23 +295,39 @@ func existsKamelet(ctx context.Context, c client.Client, 
name string, namespace
        return true
 }
 
-func (o *promoteCmdOptions) editIntegration(it *v1.Integration) 
*v1.Integration {
+func (o *promoteCmdOptions) editIntegration(it *v1.Integration) 
(*v1.Integration, error) {
        dst := v1.NewIntegration(o.To, it.Name)
        contImage := it.Status.Image
        dst.Spec = *it.Spec.DeepCopy()
-       dst.Spec.Traits = map[string]v1.TraitSpec{
-               "container": traitSpecFromMap(map[string]interface{}{
-                       "image": contImage,
-               }),
+       if dst.Spec.Traits == nil {
+               dst.Spec.Traits = map[string]v1.TraitSpec{}
        }
-
-       return &dst
+       editedContTrait, err := 
editContainerImage(dst.Spec.Traits["container"], contImage)
+       dst.Spec.Traits["container"] = editedContTrait
+       return &dst, err
 }
 
-// TODO refactor properly
-func traitSpecFromMap(spec map[string]interface{}) v1.TraitSpec {
-       var trait v1.TraitSpec
-       data, _ := json.Marshal(spec)
-       _ = json.Unmarshal(data, &trait.Configuration)
-       return trait
+func editContainerImage(contTrait v1.TraitSpec, image string) (v1.TraitSpec, 
error) {
+       var editedTrait v1.TraitSpec
+       m := make(map[string]map[string]interface{})
+       data, err := json.Marshal(contTrait)
+       if err != nil {
+               return editedTrait, err
+       }
+       err = json.Unmarshal(data, &m)
+       if err != nil {
+               return editedTrait, err
+       }
+       // We must initialize, if it was not initialized so far
+       if m["configuration"] == nil {
+               m["configuration"] = make(map[string]interface{})
+       }
+       m["configuration"]["image"] = image
+       newData, err := json.Marshal(m)
+       if err != nil {
+               return editedTrait, err
+       }
+       err = json.Unmarshal(newData, &editedTrait)
+
+       return editedTrait, err
 }
diff --git a/pkg/cmd/promote_test.go b/pkg/cmd/promote_test.go
index ef527248a..3fe2331b5 100644
--- a/pkg/cmd/promote_test.go
+++ b/pkg/cmd/promote_test.go
@@ -18,36 +18,43 @@ limitations under the License.
 package cmd
 
 import (
+       "encoding/json"
        "testing"
 
-       "github.com/apache/camel-k/pkg/util/test"
-       "github.com/spf13/cobra"
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/stretchr/testify/assert"
 )
 
-const cmdPromote = "promote"
+func TestEditContainerTrait(t *testing.T) {
+       var containerTrait v1.TraitSpec
+       m := make(map[string]interface{})
+       m["configuration"] = map[string]interface{}{
+               "name":  "myName",
+               "image": "myImage",
+       }
+       data, _ := json.Marshal(m)
+       _ = json.Unmarshal(data, &containerTrait)
 
-// nolint: unparam
-func initializePromoteCmdOptions(t *testing.T) (*promoteCmdOptions, 
*cobra.Command, RootCmdOptions) {
-       t.Helper()
+       editedContainerTrait, err := editContainerImage(containerTrait, 
"editedImage")
+       assert.Nil(t, err)
 
-       options, rootCmd := kamelTestPreAddCommandInit()
-       promoteCmdOptions := addTestPromoteCmd(*options, rootCmd)
-       kamelTestPostAddCommandInit(t, rootCmd)
+       mappedTrait := make(map[string]map[string]interface{})
+       newData, _ := json.Marshal(editedContainerTrait)
+       _ = json.Unmarshal(newData, &mappedTrait)
 
-       return promoteCmdOptions, rootCmd, *options
+       assert.Equal(t, "myName", mappedTrait["configuration"]["name"])
+       assert.Equal(t, "editedImage", mappedTrait["configuration"]["image"])
 }
 
-// nolint: unparam
-func addTestPromoteCmd(options RootCmdOptions, rootCmd *cobra.Command) 
*promoteCmdOptions {
-       // add a testing version of operator Command
-       operatorCmd, promoteOptions := newCmdPromote(&options)
-       operatorCmd.RunE = func(c *cobra.Command, args []string) error {
-               return nil
-       }
-       operatorCmd.PostRunE = func(c *cobra.Command, args []string) error {
-               return nil
-       }
-       operatorCmd.Args = test.ArbitraryArgs
-       rootCmd.AddCommand(operatorCmd)
-       return promoteOptions
+func TestEditMissingContainerTrait(t *testing.T) {
+       var containerTrait v1.TraitSpec
+
+       editedContainerTrait, err := editContainerImage(containerTrait, 
"editedImage")
+       assert.Nil(t, err)
+
+       mappedTrait := make(map[string]map[string]interface{})
+       newData, _ := json.Marshal(editedContainerTrait)
+       _ = json.Unmarshal(newData, &mappedTrait)
+
+       assert.Equal(t, "editedImage", mappedTrait["configuration"]["image"])
 }

Reply via email to