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 <16694899+rinaldo...@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),