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

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


The following commit(s) were added to refs/heads/main by this push:
     new 37a9fadb9 feat(cli): Add add-repo command to add a repo for custom 
Kamelet catalog
37a9fadb9 is described below

commit 37a9fadb90003aa2344d6d700d90bf6a1bef3849
Author: Nicolas Filotto <nfilo...@talend.com>
AuthorDate: Thu Sep 8 19:18:31 2022 +0200

    feat(cli): Add add-repo command to add a repo for custom Kamelet catalog
---
 .../files/TimerCustomKameletIntegration.java       |  28 ++++++
 .../kamelets/timer-custom-source.kamelet.yaml      |  69 +++++++++++++
 e2e/global/common/kamelet_test.go                  |  43 +++++---
 pkg/cmd/kamelet.go                                 |   1 +
 pkg/cmd/kamelet_add_repo.go                        | 111 +++++++++++++++++++++
 pkg/cmd/kamelet_add_repo_test.go                   |  90 +++++++++++++++++
 pkg/cmd/kamelet_get.go                             |   5 +-
 pkg/cmd/kit_get.go                                 |   5 +-
 8 files changed, 332 insertions(+), 20 deletions(-)

diff --git a/e2e/global/common/files/TimerCustomKameletIntegration.java 
b/e2e/global/common/files/TimerCustomKameletIntegration.java
new file mode 100644
index 000000000..9f749d834
--- /dev/null
+++ b/e2e/global/common/files/TimerCustomKameletIntegration.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+import java.lang.Exception;
+import java.lang.Override;
+import org.apache.camel.builder.RouteBuilder;
+
+public class TimerCustomKameletIntegration extends RouteBuilder {
+    @Override
+    public void configure() throws Exception {
+        from("kamelet:timer-custom-source?message=great%20message")
+            .to("log:info");
+    }
+}
diff --git a/e2e/global/common/files/kamelets/timer-custom-source.kamelet.yaml 
b/e2e/global/common/files/kamelets/timer-custom-source.kamelet.yaml
new file mode 100644
index 000000000..a47e62e0d
--- /dev/null
+++ b/e2e/global/common/files/kamelets/timer-custom-source.kamelet.yaml
@@ -0,0 +1,69 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: timer-custom-source
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "main-SNAPSHOT"
+    camel.apache.org/kamelet.icon: 
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gU3ZnIFZlY3RvciBJY29ucyA6IGh0dHA6Ly93d3cub25saW5ld2ViZm9udHMuY29tL2ljb24gLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm9
 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Timer"
+  labels:
+    camel.apache.org/kamelet.type: source
+    camel.apache.org/kamelet.verified: "true"
+spec:
+  definition:
+    title: Timer Custom Source
+    description: Produces periodic events with a custom payload in a custom 
way.
+    required:
+      - message
+    type: object
+    properties:
+      period:
+        title: Period
+        description: The interval between two events in milliseconds
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+        example: hello world
+      contentType:
+        title: Content Type
+        description: The content type of the message being generated
+        type: string
+        default: text/plain
+  dependencies:
+    - "camel:core"
+    - "camel:timer"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - set-header:
+            name: "Content-Type"
+            constant: "{{contentType}}"
+        - to: kamelet:sink
diff --git a/e2e/global/common/kamelet_test.go 
b/e2e/global/common/kamelet_test.go
index d255f7319..de91fbe80 100644
--- a/e2e/global/common/kamelet_test.go
+++ b/e2e/global/common/kamelet_test.go
@@ -41,18 +41,37 @@ func TestKameletClasspathLoading(t *testing.T) {
 
                Eventually(Kamelet(kameletName, ns)).Should(BeNil())
 
-               Expect(KamelRunWithID(operatorID, ns, 
"files/TimerKameletIntegration.java", "-t", "kamelets.enabled=false",
-                       "--resource", 
"file:files/timer-source.kamelet.yaml@/kamelets/timer-source.kamelet.yaml",
-                       "-p camel.component.kamelet.location=file:/kamelets",
-                       "-d", "camel:yaml-dsl",
-                       // kamelet dependencies
-                       "-d", "camel:timer").Execute()).To(Succeed())
-               Eventually(IntegrationPodPhase(ns, 
"timer-kamelet-integration"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
-
-               Eventually(IntegrationLogs(ns, 
"timer-kamelet-integration")).Should(ContainSubstring("important message"))
-
-               // Cleanup
-               Expect(Kamel("delete", "--all", "-n", 
ns).Execute()).Should(BeNil())
+               // Basic
+               t.Run("test basic case", func(t *testing.T) {
+
+                       Expect(KamelRunWithID(operatorID, ns, 
"files/TimerKameletIntegration.java", "-t", "kamelets.enabled=false",
+                               "--resource", 
"file:files/timer-source.kamelet.yaml@/kamelets/timer-source.kamelet.yaml",
+                               "-p 
camel.component.kamelet.location=file:/kamelets",
+                               "-d", "camel:yaml-dsl",
+                               // kamelet dependencies
+                               "-d", "camel:timer").Execute()).To(Succeed())
+                       Eventually(IntegrationPodPhase(ns, 
"timer-kamelet-integration"), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+
+                       Eventually(IntegrationLogs(ns, 
"timer-kamelet-integration")).Should(ContainSubstring("important message"))
+
+                       // Cleanup
+                       Expect(Kamel("delete", "--all", "-n", 
ns).Execute()).Should(BeNil())
+               })
+
+               // Custom repo
+               t.Run("test custom Kamelet repository", func(t *testing.T) {
+
+                       // Add the custom repository
+                       Expect(Kamel("kamelet", "add-repo", 
"github:essobedo/camel-k-test/kamelets", "-n", ns, "-x", 
operatorID).Execute()).To(Succeed())
+
+                       Expect(KamelRunWithID(operatorID, ns, 
"files/TimerCustomKameletIntegration.java").Execute()).To(Succeed())
+                       Eventually(IntegrationPodPhase(ns, 
"timer-custom-kamelet-integration"), 
TestTimeoutLong).Should(Equal(corev1.PodRunning))
+
+                       Eventually(IntegrationLogs(ns, 
"timer-custom-kamelet-integration")).Should(ContainSubstring("great message"))
+
+                       // Cleanup
+                       Expect(Kamel("delete", "--all", "-n", 
ns).Execute()).Should(BeNil())
+               })
        })
 }
 
diff --git a/pkg/cmd/kamelet.go b/pkg/cmd/kamelet.go
index 89a09062c..7645fd0c2 100644
--- a/pkg/cmd/kamelet.go
+++ b/pkg/cmd/kamelet.go
@@ -30,6 +30,7 @@ func newCmdKamelet(rootCmdOptions *RootCmdOptions) 
*cobra.Command {
 
        cmd.AddCommand(cmdOnly(newKameletGetCmd(rootCmdOptions)))
        cmd.AddCommand(cmdOnly(newKameletDeleteCmd(rootCmdOptions)))
+       cmd.AddCommand(cmdOnly(newKameletAddRepoCmd(rootCmdOptions)))
 
        return &cmd
 }
diff --git a/pkg/cmd/kamelet_add_repo.go b/pkg/cmd/kamelet_add_repo.go
new file mode 100644
index 000000000..92591823d
--- /dev/null
+++ b/pkg/cmd/kamelet_add_repo.go
@@ -0,0 +1,111 @@
+/*
+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 cmd
+
+import (
+       "errors"
+       "fmt"
+       "regexp"
+
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/spf13/cobra"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// kameletRepositoryURIRegexp is the regular expression used to validate the 
URI of a Kamelet repository.
+var kameletRepositoryURIRegexp = 
regexp.MustCompile(`^github:[^/]+/[^/]+((/[^/]+)*)?$`)
+
+func newKameletAddRepoCmd(rootCmdOptions *RootCmdOptions) (*cobra.Command, 
*kameletAddRepoCommandOptions) {
+       options := kameletAddRepoCommandOptions{
+               RootCmdOptions: rootCmdOptions,
+       }
+
+       cmd := cobra.Command{
+               Use:     "add-repo 
github:owner/repo[/path_to_kamelets_folder][@version] ...",
+               Short:   "Add a Kamelet repository",
+               Long:    `Add a Kamelet repository.`,
+               PreRunE: decode(&options),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       if err := options.validate(args); err != nil {
+                               return err
+                       }
+                       return options.run(cmd, args)
+               },
+       }
+
+       cmd.Flags().StringP("operator-id", "x", "camel-k", "Id of the Operator 
to update.")
+
+       return &cmd, &options
+}
+
+type kameletAddRepoCommandOptions struct {
+       *RootCmdOptions
+       OperatorID string `mapstructure:"operator-id" yaml:",omitempty"`
+}
+
+func (o *kameletAddRepoCommandOptions) validate(args []string) error {
+       if len(args) == 0 {
+               return errors.New("at least one Kamelet repository is expected")
+       }
+       if o.OperatorID == "" {
+               return fmt.Errorf("cannot use empty operator id")
+       }
+       return nil
+}
+
+func (o *kameletAddRepoCommandOptions) run(cmd *cobra.Command, args []string) 
error {
+       c, err := o.GetCmdClient()
+       if err != nil {
+               return err
+       }
+       key := client.ObjectKey{
+               Namespace: o.Namespace,
+               Name:      o.OperatorID,
+       }
+       platform := v1.IntegrationPlatform{}
+       if err := c.Get(o.Context, key, &platform); err != nil {
+               if k8serrors.IsNotFound(err) {
+                       // IntegrationPlatform may be in the operator 
namespace, but we currently don't have a way to determine it: we just warn
+                       fmt.Fprintf(cmd.ErrOrStderr(), "Warning: 
IntegrationPlatform %q not found in namespace %q\n", key.Name, key.Namespace)
+                       return nil
+               }
+               return err
+       }
+       for _, uri := range args {
+               if err := checkURI(uri, platform.Spec.Kamelet.Repositories); 
err != nil {
+                       return err
+               }
+               platform.Spec.Kamelet.Repositories = 
append(platform.Spec.Kamelet.Repositories, 
v1.IntegrationPlatformKameletRepositorySpec{
+                       URI: uri,
+               })
+       }
+       return c.Update(o.Context, &platform)
+}
+
+func checkURI(uri string, repositories 
[]v1.IntegrationPlatformKameletRepositorySpec) error {
+       if !kameletRepositoryURIRegexp.MatchString(uri) {
+               return fmt.Errorf("malformed Kamelet repository uri %s, the 
expected format is github:owner/repo[/path_to_kamelets_folder][@version]", uri)
+       }
+       for _, repo := range repositories {
+               if repo.URI == uri {
+                       return fmt.Errorf("duplicate Kamelet repository uri 
%s", uri)
+               }
+       }
+       return nil
+}
diff --git a/pkg/cmd/kamelet_add_repo_test.go b/pkg/cmd/kamelet_add_repo_test.go
new file mode 100644
index 000000000..fcaeeac6c
--- /dev/null
+++ b/pkg/cmd/kamelet_add_repo_test.go
@@ -0,0 +1,90 @@
+/*
+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 cmd
+
+import (
+       "testing"
+
+       "github.com/spf13/cobra"
+       "github.com/stretchr/testify/assert"
+
+       v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/pkg/util/test"
+)
+
+const cmdKameletAddRepo = "add-repo"
+
+// nolint: unparam
+func initializeKameletAddRepoCmdOptions(t *testing.T) 
(*kameletAddRepoCommandOptions, *cobra.Command, RootCmdOptions) {
+       t.Helper()
+
+       options, rootCmd := kamelTestPreAddCommandInit()
+       kameletAddRepoCommandOptions := addTestKameletAddRepoCmd(*options, 
rootCmd)
+       kamelTestPostAddCommandInit(t, rootCmd)
+
+       return kameletAddRepoCommandOptions, rootCmd, *options
+}
+
+func addTestKameletAddRepoCmd(options RootCmdOptions, rootCmd *cobra.Command) 
*kameletAddRepoCommandOptions {
+       // Add a testing version of kamelet add-repo Command
+       kameletAddRepoCmd, kameletAddRepoOptions := 
newKameletAddRepoCmd(&options)
+       kameletAddRepoCmd.RunE = func(c *cobra.Command, args []string) error {
+               return nil
+       }
+       kameletAddRepoCmd.PostRunE = func(c *cobra.Command, args []string) 
error {
+               return nil
+       }
+       kameletAddRepoCmd.Args = test.ArbitraryArgs
+       rootCmd.AddCommand(kameletAddRepoCmd)
+       return kameletAddRepoOptions
+}
+
+func TestKameletAddRepoNoFlag(t *testing.T) {
+       _, rootCmd, _ := initializeKameletAddRepoCmdOptions(t)
+       _, err := test.ExecuteCommand(rootCmd, cmdKameletAddRepo, "foo")
+       assert.Nil(t, err)
+}
+
+func TestKameletAddRepoNonExistingFlag(t *testing.T) {
+       _, rootCmd, _ := initializeKameletAddRepoCmdOptions(t)
+       _, err := test.ExecuteCommand(rootCmd, cmdKameletAddRepo, 
"--nonExistingFlag", "foo")
+       assert.NotNil(t, err)
+}
+
+func TestKameletAddRepoInvalidRepositoryURI(t *testing.T) {
+       repositories := []v1.IntegrationPlatformKameletRepositorySpec{}
+       assert.NotNil(t, checkURI("foo", repositories))
+       assert.NotNil(t, checkURI("github", repositories))
+       assert.NotNil(t, checkURI("github:", repositories))
+       assert.NotNil(t, checkURI("github:foo", repositories))
+       assert.NotNil(t, checkURI("github:foo/", repositories))
+}
+
+func TestKameletAddRepoValidRepositoryURI(t *testing.T) {
+       repositories := []v1.IntegrationPlatformKameletRepositorySpec{}
+       assert.Nil(t, checkURI("github:foo/bar", repositories))
+       assert.Nil(t, checkURI("github:foo/bar/some/path", repositories))
+       assert.Nil(t, checkURI("github:foo/bar@1.0", repositories))
+       assert.Nil(t, checkURI("github:foo/bar/some/path@1.0", repositories))
+}
+
+func TestKameletAddRepoDuplicateRepositoryURI(t *testing.T) {
+       repositories := []v1.IntegrationPlatformKameletRepositorySpec{{URI: 
"github:foo/bar"}}
+       assert.NotNil(t, checkURI("github:foo/bar", repositories))
+       assert.Nil(t, checkURI("github:foo/bar2", repositories))
+}
diff --git a/pkg/cmd/kamelet_get.go b/pkg/cmd/kamelet_get.go
index 013e92c0f..ced23216a 100644
--- a/pkg/cmd/kamelet_get.go
+++ b/pkg/cmd/kamelet_get.go
@@ -44,11 +44,8 @@ func newKameletGetCmd(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *kameletG
                        if err := options.validate(); err != nil {
                                return err
                        }
-                       if err := options.run(cmd); err != nil {
-                               fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
-                       }
 
-                       return nil
+                       return options.run(cmd)
                },
        }
 
diff --git a/pkg/cmd/kit_get.go b/pkg/cmd/kit_get.go
index e74864897..47f78268a 100644
--- a/pkg/cmd/kit_get.go
+++ b/pkg/cmd/kit_get.go
@@ -42,11 +42,8 @@ func newKitGetCmd(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *kitGetComman
                        if err := options.validate(cmd, args); err != nil {
                                return err
                        }
-                       if err := options.run(cmd); err != nil {
-                               fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
-                       }
 
-                       return nil
+                       return options.run(cmd)
                },
        }
 

Reply via email to