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 2023/10/25 13:33:58 UTC

[camel-k] 01/02: feat(runtime): enable source less integrations

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 27e07a2fdde207099c2f7ce96b8ca0265aa07a27
Author: Pasquale Congiusti <pa...@gmail.com>
AuthorDate: Tue Oct 17 17:28:22 2023 +0200

    feat(runtime): enable source less integrations
    
    Closes #3295
---
 e2e/common/runtimes/default.go       | 26 ++++++++++++++
 e2e/common/runtimes/runtimes_test.go | 70 ++++++++++++++++++++++++++++++++++++
 pkg/cmd/run.go                       | 30 +++++++++++-----
 pkg/cmd/run_test.go                  | 34 ++++++++++++++----
 pkg/util/kubernetes/sanitize_test.go |  2 ++
 script/Makefile                      |  1 +
 6 files changed, 149 insertions(+), 14 deletions(-)

diff --git a/e2e/common/runtimes/default.go b/e2e/common/runtimes/default.go
new file mode 100644
index 000000000..1cc41c3f9
--- /dev/null
+++ b/e2e/common/runtimes/default.go
@@ -0,0 +1,26 @@
+//go:build integration
+// +build 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 runtimes
+
+import "github.com/apache/camel-k/v2/e2e/support"
+
+var ns = support.GetEnvOrDefault("CAMEL_K_TEST_NAMESPACE", support.GetCIProcessID())
+var operatorID = support.GetEnvOrDefault("CAMEL_K_OPERATOR_ID", support.GetCIProcessID())
diff --git a/e2e/common/runtimes/runtimes_test.go b/e2e/common/runtimes/runtimes_test.go
new file mode 100644
index 000000000..d95c6820c
--- /dev/null
+++ b/e2e/common/runtimes/runtimes_test.go
@@ -0,0 +1,70 @@
+//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 runtimes
+
+import (
+	"testing"
+
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/v2/e2e/support"
+	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+)
+
+func TestSourceLessIntegrations(t *testing.T) {
+	RegisterTestingT(t)
+	var cmData = make(map[string]string)
+	cmData["my-file.txt"] = "Hello World!"
+	CreatePlainTextConfigmap(ns, "my-cm-sourceless", cmData)
+
+	t.Run("Camel Main", func(t *testing.T) {
+		itName := "my-camel-main-v1"
+		Expect(KamelRunWithID(operatorID, ns, "--image", "docker.io/squakez/my-camel-main:1.0.0", "--resource", "configmap:my-cm-sourceless@/tmp/app/data").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, itName), TestTimeoutShort).Should(Equal(corev1.PodRunning))
+		Eventually(IntegrationConditionStatus(ns, itName, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring(cmData["my-file.txt"]))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring("Apache Camel (Main)"))
+	})
+
+	t.Run("Camel Spring Boot", func(t *testing.T) {
+		itName := "my-camel-sb-v1"
+		Expect(KamelRunWithID(operatorID, ns, "--image", "docker.io/squakez/my-camel-sb:1.0.0", "--resource", "configmap:my-cm-sourceless@/tmp/app/data").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, itName), TestTimeoutShort).Should(Equal(corev1.PodRunning))
+		Eventually(IntegrationConditionStatus(ns, itName, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring(cmData["my-file.txt"]))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring("Spring Boot"))
+	})
+
+	t.Run("Camel Quarkus", func(t *testing.T) {
+		itName := "my-camel-quarkus-v1"
+		Expect(KamelRunWithID(operatorID, ns, "--image", "docker.io/squakez/my-camel-quarkus:1.0.0", "--resource", "configmap:my-cm-sourceless@/tmp/app/data").Execute()).To(Succeed())
+		Eventually(IntegrationPodPhase(ns, itName), TestTimeoutShort).Should(Equal(corev1.PodRunning))
+		Eventually(IntegrationConditionStatus(ns, itName, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring(cmData["my-file.txt"]))
+		Eventually(IntegrationLogs(ns, itName), TestTimeoutShort).Should(ContainSubstring("powered by Quarkus"))
+	})
+
+	Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed())
+}
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index f117f8138..ea8ba4710 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -98,6 +98,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) (*cobra.Command, *runCmdOptions)
 	}
 
 	cmd.Flags().String("name", "", "The integration name")
+	cmd.Flags().String("image", "", "An image built externally (ie, via CICD). Enabling it will skip the Integration build phase.")
 	cmd.Flags().StringArrayP("connect", "c", nil, "A Service that the integration should bind to, specified as [[apigroup/]version:]kind:[namespace/]name")
 	cmd.Flags().StringArrayP("dependency", "d", nil, usageDependency)
 	cmd.Flags().BoolP("wait", "w", false, "Wait for the integration to be running")
@@ -145,6 +146,7 @@ type runCmdOptions struct {
 	Save            bool     `mapstructure:"save" yaml:",omitempty" kamel:"omitsave"`
 	IntegrationKit  string   `mapstructure:"kit" yaml:",omitempty"`
 	IntegrationName string   `mapstructure:"name" yaml:",omitempty"`
+	ContainerImage  string   `mapstructure:"image" yaml:",omitempty"`
 	Profile         string   `mapstructure:"profile" yaml:",omitempty"`
 	OperatorID      string   `mapstructure:"operator-id" yaml:",omitempty"`
 	OutputFormat    string   `mapstructure:"output" yaml:",omitempty"`
@@ -236,10 +238,6 @@ func (o *runCmdOptions) decode(cmd *cobra.Command, args []string) error {
 }
 
 func (o *runCmdOptions) validateArgs(cmd *cobra.Command, args []string) error {
-	if len(args) < 1 {
-		return errors.New("run expects at least 1 argument, received 0")
-	}
-
 	if _, err := source.Resolve(context.Background(), args, false, cmd); err != nil {
 		return fmt.Errorf("one of the provided sources is not reachable: %w", err)
 	}
@@ -324,6 +322,11 @@ func (o *runCmdOptions) run(cmd *cobra.Command, args []string) error {
 		}
 	}
 
+	// We need to make this check at this point, in order to have sources filled during decoding
+	if len(args) < 1 && o.ContainerImage == "" {
+		return errors.New("run command expects either an Integration source or the container image (via --image argument)")
+	}
+
 	integration, err := o.createOrUpdateIntegration(cmd, c, args)
 	if err != nil {
 		return err
@@ -536,8 +539,15 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 		return nil, err
 	}
 
-	if err := o.resolveSources(cmd, sources, integration); err != nil {
-		return nil, err
+	if o.ContainerImage == "" {
+		// Resolve resources
+		if err := o.resolveSources(cmd, sources, integration); err != nil {
+			return nil, err
+		}
+	} else {
+		// Source-less Integration as the user provided a container image built externally
+		o.Traits = append(o.Traits, fmt.Sprintf("container.image=%s", o.ContainerImage))
+		o.Traits = append(o.Traits, "jvm.enabled=false")
 	}
 
 	if err := resolvePodTemplate(context.Background(), cmd, o.PodTemplate, &integration.Spec); err != nil {
@@ -868,11 +878,15 @@ func (o *runCmdOptions) getPlatform(cmd *cobra.Command, c client.Client, it *v1.
 
 func (o *runCmdOptions) GetIntegrationName(sources []string) string {
 	name := ""
-	if o.IntegrationName != "" {
+	switch {
+	case o.IntegrationName != "":
 		name = o.IntegrationName
 		name = kubernetes.SanitizeName(name)
-	} else if len(sources) == 1 {
+	case len(sources) == 1:
 		name = kubernetes.SanitizeName(sources[0])
+	case o.ContainerImage != "":
+		// source-less execution
+		name = kubernetes.SanitizeName(strings.ReplaceAll(o.ContainerImage, ":", "-v"))
 	}
 	return name
 }
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index becac009b..6ca17a838 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -511,13 +511,9 @@ func TestRunBuildPropertyFlag(t *testing.T) {
 
 func TestRunValidateArgs(t *testing.T) {
 	runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
-	args := []string{}
-	err := runCmdOptions.validateArgs(rootCmd, args)
-	assert.NotNil(t, err)
-	assert.Equal(t, "run expects at least 1 argument, received 0", err.Error())
 
-	args = []string{"run_test.go"}
-	err = runCmdOptions.validateArgs(rootCmd, args)
+	args := []string{"run_test.go"}
+	err := runCmdOptions.validateArgs(rootCmd, args)
 	assert.Nil(t, err)
 
 	args = []string{"missing_file"}
@@ -826,3 +822,29 @@ func TestRunOutputWithoutKubernetesCluster(t *testing.T) {
 	_, err = test.ExecuteCommand(rootCmd, cmdRun, "-o", "yaml", integrationSource)
 	require.NoError(t, err)
 }
+
+func TestSourceLessIntegration(t *testing.T) {
+	runCmdOptions, runCmd, _ := initializeRunCmdOptionsWithOutput(t)
+	output, err := test.ExecuteCommand(runCmd, cmdRun, "--image", "docker.io/my-org/my-app:1.0.0", "-o", "yaml", "-t", "mount.configs=configmap:my-cm")
+	assert.Equal(t, "yaml", runCmdOptions.OutputFormat)
+
+	assert.Nil(t, err)
+	assert.Equal(t, `apiVersion: camel.apache.org/v1
+kind: Integration
+metadata:
+  annotations:
+    camel.apache.org/operator.id: camel-k
+  creationTimestamp: null
+  name: my-app-v1
+spec:
+  traits:
+    container:
+      image: docker.io/my-org/my-app:1.0.0
+    jvm:
+      enabled: false
+    mount:
+      configs:
+      - configmap:my-cm
+status: {}
+`, output)
+}
diff --git a/pkg/util/kubernetes/sanitize_test.go b/pkg/util/kubernetes/sanitize_test.go
index 31b422e20..93bcac937 100644
--- a/pkg/util/kubernetes/sanitize_test.go
+++ b/pkg/util/kubernetes/sanitize_test.go
@@ -36,6 +36,8 @@ func TestSanitizeName(t *testing.T) {
 		{"input": "-foo-bar-", "expect": "foo-bar"},
 		{"input": "1foo-bar2", "expect": "1foo-bar2"},
 		{"input": "foo-bar-1", "expect": "foo-bar-1"},
+		{"input": "docker.io/squakez/my-camel-sb:1.0.0", "expect": "my-camel-sb1"},
+		{"input": "docker.io/squakez/my-camel-sb:2.0.0", "expect": "my-camel-sb2"},
 	}
 
 	for _, c := range cases {
diff --git a/script/Makefile b/script/Makefile
index ac59c1692..786ced9ca 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -260,6 +260,7 @@ test-common: do-build
 	go test -timeout 30m -v ./e2e/common/config -tags=integration $(TEST_INTEGRATION_COMMON_LANG_RUN) $(GOTESTFMT) || FAILED=1; \
 	go test -timeout 30m -v ./e2e/common/misc -tags=integration $(TEST_INTEGRATION_COMMON_LANG_RUN) $(GOTESTFMT) || FAILED=1; \
 	go test -timeout 60m -v ./e2e/common/traits -tags=integration $(TEST_INTEGRATION_COMMON_LANG_RUN) $(GOTESTFMT) || FAILED=1; \
+	go test -timeout 20m -v ./e2e/common/runtimes -tags=integration $(TEST_INTEGRATION_COMMON_LANG_RUN) $(GOTESTFMT) || FAILED=1; \
 	go test -timeout 10m -v ./e2e/common/support/teardown_test.go -tags=integration $(TEST_INTEGRATION_COMMON_LANG_RUN) $(GOTESTFMT) || FAILED=1; \
 	exit $${FAILED}