You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2024/01/04 15:45:46 UTC

(camel-k) branch main updated: feat: add support for glob pattern in run sources

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


The following commit(s) were added to refs/heads/main by this push:
     new bf0e79b97 feat: add support for glob pattern in run sources
bf0e79b97 is described below

commit bf0e79b973344e12f556ba9e170c0eb9f8130771
Author: Rinaldo Pitzer Jr <16...@users.noreply.github.com>
AuthorDate: Tue Jan 2 15:02:07 2024 -0300

    feat: add support for glob pattern in run sources
---
 e2e/common/cli/files/glob/Java1.java |  27 +++++++++
 e2e/common/cli/files/glob/Java2.java |  27 +++++++++
 e2e/common/cli/files/glob/run1.yaml  |  25 ++++++++
 e2e/common/cli/files/glob/run2.yaml  |  25 ++++++++
 e2e/common/cli/run_test.go           |  40 +++++++++++++
 pkg/cmd/run_test.go                  | 113 +++++++++++++++++++++++++++++++++++
 pkg/cmd/source/source.go             |  34 +++++++++++
 pkg/cmd/source/util.go               |  20 +++++++
 pkg/resources/resources.go           |   8 +--
 9 files changed, 315 insertions(+), 4 deletions(-)

diff --git a/e2e/common/cli/files/glob/Java1.java b/e2e/common/cli/files/glob/Java1.java
new file mode 100644
index 000000000..554fb9473
--- /dev/null
+++ b/e2e/common/cli/files/glob/Java1.java
@@ -0,0 +1,27 @@
+/*
+ * 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 org.apache.camel.builder.RouteBuilder;
+
+public class Java1 extends RouteBuilder {
+  @Override
+  public void configure() throws Exception {
+	  from("timer:tick?period=5000")
+	  .setBody().simple("Hello java 1 {{property:default}}")
+      .log("${body}");
+  }
+}
diff --git a/e2e/common/cli/files/glob/Java2.java b/e2e/common/cli/files/glob/Java2.java
new file mode 100644
index 000000000..1c8e2d714
--- /dev/null
+++ b/e2e/common/cli/files/glob/Java2.java
@@ -0,0 +1,27 @@
+/*
+ * 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 org.apache.camel.builder.RouteBuilder;
+
+public class Java2 extends RouteBuilder {
+  @Override
+  public void configure() throws Exception {
+	  from("timer:tick?period=6000")
+	  .setBody().simple("Hello java 2 {{property:default}}")
+      .log("${body}");
+  }
+}
diff --git a/e2e/common/cli/files/glob/run1.yaml b/e2e/common/cli/files/glob/run1.yaml
new file mode 100644
index 000000000..10e1fc1eb
--- /dev/null
+++ b/e2e/common/cli/files/glob/run1.yaml
@@ -0,0 +1,25 @@
+# ---------------------------------------------------------------------------
+# 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:
+    uri: "timer:yaml"
+    parameters:
+      period: "5000"
+    steps:
+      - setBody:
+          simple: "Hello run 1 {{property:default}}"
+      - to: "log:info"
diff --git a/e2e/common/cli/files/glob/run2.yaml b/e2e/common/cli/files/glob/run2.yaml
new file mode 100644
index 000000000..6590aad9a
--- /dev/null
+++ b/e2e/common/cli/files/glob/run2.yaml
@@ -0,0 +1,25 @@
+# ---------------------------------------------------------------------------
+# 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:
+    uri: "timer:yaml"
+    parameters:
+      period: "6000"
+    steps:
+      - setBody:
+          simple: "Hello run 2 {{property:default}}"
+      - to: "log:info"
diff --git a/e2e/common/cli/run_test.go b/e2e/common/cli/run_test.go
index d92c78d04..81ccbd330 100644
--- a/e2e/common/cli/run_test.go
+++ b/e2e/common/cli/run_test.go
@@ -138,6 +138,46 @@ func TestKamelCLIRun(t *testing.T) {
 		Eventually(DeleteIntegrations(ns), TestTimeoutLong).Should(Equal(0))
 	})
 
+	t.Run("Run with glob patterns", func(t *testing.T) {
+		t.Run("YAML", func(t *testing.T) {
+			name := RandomizedSuffixName("run")
+			Expect(KamelRunWithID(operatorID, ns, "files/glob/run*", "--name", name).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).
+				Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello run 1 default"))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello run 2 default"))
+			Eventually(DeleteIntegrations(ns), TestTimeoutLong).Should(Equal(0))
+		})
+
+		t.Run("Java", func(t *testing.T) {
+			name := RandomizedSuffixName("java")
+			Expect(KamelRunWithID(operatorID, ns, "files/glob/Java*", "--name", name).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).
+				Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello java 1 default"))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello java 2 default"))
+			Eventually(DeleteIntegrations(ns), TestTimeoutLong).Should(Equal(0))
+		})
+
+		t.Run("All", func(t *testing.T) {
+			name := RandomizedSuffixName("java")
+			Expect(KamelRunWithID(operatorID, ns, "files/glob/*", "--name", name).Execute()).To(Succeed())
+			Eventually(IntegrationPodPhase(ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning))
+			Eventually(IntegrationConditionStatus(ns, name, v1.IntegrationConditionReady), TestTimeoutShort).
+				Should(Equal(corev1.ConditionTrue))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello run 1 default"))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello run 2 default"))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello java 1 default"))
+			Eventually(IntegrationLogs(ns, name), TestTimeoutShort).Should(ContainSubstring("Hello java 2 default"))
+			Eventually(DeleteIntegrations(ns), TestTimeoutLong).Should(Equal(0))
+		})
+
+		// Clean up
+		Eventually(DeleteIntegrations(ns), TestTimeoutLong).Should(Equal(0))
+	})
+
 	/*
 	 * TODO
 	 * The dependency cannot be read by maven while building. See #3708
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 43eb732f9..4ecc9fd1b 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -812,6 +812,119 @@ func TestRunOutput(t *testing.T) {
 	assert.Equal(t, fmt.Sprintf("Integration \"%s\" updated\n", integrationName), output)
 }
 
+func TestRunGlob(t *testing.T) {
+	dir, err := os.MkdirTemp("", "camel-k-TestRunGlob-*")
+	if err != nil {
+		t.Error(err)
+	}
+
+	pattern := "camel-k-*.yaml"
+
+	tmpFile1, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile1.Close()
+	assert.Nil(t, tmpFile1.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile1.Name(), []byte(yamlIntegration), 0o400))
+
+	tmpFile2, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile2.Close()
+	assert.Nil(t, tmpFile2.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile2.Name(), []byte(yamlIntegration), 0o400))
+
+	integrationName := "myname"
+
+	_, rootCmd, _ := initializeRunCmdOptionsWithOutput(t)
+
+	file := fmt.Sprintf("%s%c%s*", dir, os.PathSeparator, "camel-k-*") // = dir/camel-k-*
+
+	output, err := test.ExecuteCommand(rootCmd, cmdRun, "--name", integrationName, file)
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" created\n", integrationName), output)
+}
+
+func TestRunGlobAllFiles(t *testing.T) {
+	dir, err := os.MkdirTemp("", "camel-k-TestRunGlobAllFiles-*")
+	if err != nil {
+		t.Error(err)
+	}
+
+	pattern := "camel-k-*.yaml"
+
+	tmpFile1, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile1.Close()
+	assert.Nil(t, tmpFile1.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile1.Name(), []byte(yamlIntegration), 0o400))
+
+	tmpFile2, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile2.Close()
+	assert.Nil(t, tmpFile2.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile2.Name(), []byte(yamlIntegration), 0o400))
+
+	integrationName := "myname"
+
+	_, rootCmd, _ := initializeRunCmdOptionsWithOutput(t)
+
+	file := fmt.Sprintf("%s%c*", dir, os.PathSeparator) // = dir/*
+
+	output, err := test.ExecuteCommand(rootCmd, cmdRun, "--name", integrationName, file)
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" created\n", integrationName), output)
+}
+
+func TestRunGlobChange(t *testing.T) {
+	dir, err := os.MkdirTemp("", "camel-k-TestRunGlobChange-*")
+	if err != nil {
+		t.Error(err)
+	}
+
+	pattern := "camel-k-*.yaml"
+
+	tmpFile1, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile1.Close()
+	assert.Nil(t, tmpFile1.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile1.Name(), []byte(yamlIntegration), 0o400))
+
+	integrationName := "myname"
+
+	_, rootCmd, _ := initializeRunCmdOptionsWithOutput(t)
+
+	file := fmt.Sprintf("%s%c%s", dir, os.PathSeparator, "camel-k-*")
+
+	output, err := test.ExecuteCommand(rootCmd, cmdRun, "--name", integrationName, file)
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" created\n", integrationName), output)
+
+	output, err = test.ExecuteCommand(rootCmd, cmdRun, "--name", integrationName, file)
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" unchanged\n", integrationName), output)
+
+	tmpFile2, err := os.CreateTemp(dir, pattern)
+	if err != nil {
+		t.Error(err)
+	}
+	defer tmpFile2.Close()
+	assert.Nil(t, tmpFile2.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile2.Name(), []byte(yamlIntegration), 0o400))
+
+	output, err = test.ExecuteCommand(rootCmd, cmdRun, "--name", integrationName, file)
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" updated\n", integrationName), output)
+}
+
 func TestRunOutputWithoutKubernetesCluster(t *testing.T) {
 	tmpFile, err := os.CreateTemp("", "camel-k-kubeconfig-*")
 	require.NoError(t, err)
diff --git a/pkg/cmd/source/source.go b/pkg/cmd/source/source.go
index da93c6d7d..7a22cb21a 100644
--- a/pkg/cmd/source/source.go
+++ b/pkg/cmd/source/source.go
@@ -88,8 +88,42 @@ func (s Source) IsYaml() bool {
 	return strings.HasSuffix(s.Name, ".yaml") || strings.HasSuffix(s.Name, ".yml")
 }
 
+// globSources identifies glob patterns like sources/*.yaml and expand them into individual file paths.
+func globSources(locations []string) ([]string, error) {
+	var sources = make([]string, 0, len(locations))
+
+	for _, src := range locations {
+		glob, err := isGlobCandidate(src)
+		if err != nil {
+			return nil, err
+		}
+
+		if glob {
+			matches, err := filepath.Glob(src)
+			if err != nil {
+				return nil, err
+			}
+
+			if len(matches) > 0 {
+				sources = append(sources, matches...)
+			} else {
+				// leave the original location if there wasn't any matches
+				sources = append(sources, src)
+			}
+		} else {
+			sources = append(sources, src)
+		}
+	}
+	return sources, nil
+}
+
 // Resolve resolves sources from a variety of locations including local and remote.
 func Resolve(ctx context.Context, locations []string, compress bool, cmd *cobra.Command) ([]Source, error) {
+	locations, err := globSources(locations)
+	if err != nil {
+		return nil, err
+	}
+
 	sources := make([]Source, 0, len(locations))
 
 	for _, location := range locations {
diff --git a/pkg/cmd/source/util.go b/pkg/cmd/source/util.go
index 85e96a984..d90793d6e 100644
--- a/pkg/cmd/source/util.go
+++ b/pkg/cmd/source/util.go
@@ -38,6 +38,26 @@ func IsLocalAndFileExists(uri string) (bool, error) {
 		// it's not a local file as it matches one of the supporting schemes
 		return false, nil
 	}
+	return isExistingFile(uri)
+}
+
+// isGlobCandidate checks if the provided uri doesn't have a supported scheme prefix,
+// and is not an existing file, because then it could be a glob pattern like "sources/*.yaml".
+func isGlobCandidate(uri string) (bool, error) {
+	if hasSupportedScheme(uri) {
+		// it's not a local file as it matches one of the supporting schemes
+		return false, nil
+	}
+
+	exists, err := isExistingFile(uri)
+	if err != nil {
+		return false, err
+	}
+
+	return !exists, nil
+}
+
+func isExistingFile(uri string) (bool, error) {
 	info, err := os.Stat(uri)
 	if err != nil {
 		if os.IsNotExist(err) {
diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go
index 72d549721..ff3426a81 100644
--- a/pkg/resources/resources.go
+++ b/pkg/resources/resources.go
@@ -743,12 +743,12 @@ var assets = func() http.FileSystem {
 
 			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x53\xc1\x8e\xdb\x36\x14\xbc\xf3\x2b\x06\xd6\x25\x01\xd6\x72\xdb\x53\xe1\x9e\xdc\xcd\x6e\x2b\x34\xb0\x81\x95\xd3\x20\xc7\x67\xe9\x59\x7a\x58\x8a\x54\x1f\xa9\x55\xb6\x5f\x5f\x90\x96\xbb\x0e\xda\x63\x78\xb1\x05\x8d\xe6\xcd\xbc\x19\x16\x58\x7f\xbf\x63\x0a\x7c\x94\x86\x5d\xe0\x16\xd1\x23\xf6\x8c\xdd\x48\x4d\xcf\xa8\xfd\x39\xce\xa4\x8c\x47\x3f\xb9\x96\xa2\x78\x87\x77\xbb\xfa\xf1\x3d\x26\xd7\xb2\xc2\x3b\x86\x57\x0c\x5e\x [...]
 		},
-		"/camel-catalog-3.2.3.yaml": &vfsgen۰CompressedFileInfo{
-			name:             "camel-catalog-3.2.3.yaml",
+		"/camel-catalog-3.2.0.yaml": &vfsgen۰CompressedFileInfo{
+			name:             "camel-catalog-3.2.0.yaml",
 			modTime:          time.Time{},
 			uncompressedSize: 87987,
 
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\xbd\x4d\x77\xdb\xb8\xb2\x2e\x3c\xcf\xaf\xe0\xea\x4c\xce\x59\xef\x26\xba\x3b\xd9\xef\xe9\x7b\xfb\x8e\x1c\x27\x4e\xe2\xd8\x89\x13\x79\xa7\xb3\xf7\xa4\x17\x44\x42\x12\x24\x92\xa0\x01\x50\x96\xf3\xeb\xef\x02\x08\x7e\x8a\x2e\x8a\x74\xc1\xd7\x03\x93\x22\x0a\x4f\xa1\x9e\x02\xf1\x4d\xe0\x65\x10\xe2\xfd\xbd\x78\x19\x5c\xf1\x88\x65\x8a\xc5\x81\x16\x81\xde\xb0\xe0\x2c\xa7\xd1\x86\x05\x0b\xb1\xd2\xf7\x54\xb2\xe0\x42\x14\x59\x [...]
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x7d\x5b\x77\xdc\xb6\xb2\xe6\xbb\x7f\x05\x57\xfc\x72\xce\x9a\x4d\x24\x71\xf6\x9c\xcc\xca\x3c\xc9\xb2\x65\x5b\xb6\x6c\xd9\xad\x9d\x78\xef\x97\x2c\x34\x89\xee\x86\x9a\x24\x28\x00\x6c\xb5\xfc\xeb\x67\x01\x04\xaf\x4d\x15\x2f\x2a\x68\xf4\x20\xb2\x89\xc2\x57\xa8\xaf\x40\xdc\x09\xbc\x0c\x42\xbc\xbf\x17\x2f\x83\x4f\x3c\x62\x99\x62\x71\xa0\x45\xa0\x77\x2c\x38\xcb\x69\xb4\x63\xc1\x4a\x6c\xf4\x3d\x95\x2c\xb8\x10\x45\x16\x53\x [...]
 		},
 		"/traits.yaml": &vfsgen۰CompressedFileInfo{
 			name:             "traits.yaml",
@@ -761,7 +761,7 @@ var assets = func() http.FileSystem {
 	fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
 		fs["/addons"].(os.FileInfo),
 		fs["/builder"].(os.FileInfo),
-		fs["/camel-catalog-3.2.3.yaml"].(os.FileInfo),
+		fs["/camel-catalog-3.2.0.yaml"].(os.FileInfo),
 		fs["/crd"].(os.FileInfo),
 		fs["/manager"].(os.FileInfo),
 		fs["/prometheus"].(os.FileInfo),