diff --git a/deploy/traits.yaml b/deploy/traits.yaml index 64e7f7a60c..9f3ed550f0 100755 --- a/deploy/traits.yaml +++ b/deploy/traits.yaml @@ -446,6 +446,9 @@ traits: - name: options type: '[]string' description: A list of JVM options + - name: classpath + type: string + description: Additional JVM classpath (use `Linux` classpath separator) - name: kamelets platform: true profiles: diff --git a/docs/modules/ROOT/pages/apis/crds-html.adoc b/docs/modules/ROOT/pages/apis/crds-html.adoc index 158c2c9b71..7fe37e676d 100644 --- a/docs/modules/ROOT/pages/apis/crds-html.adoc +++ b/docs/modules/ROOT/pages/apis/crds-html.adoc @@ -3688,6 +3688,7 @@ string CamelArtifactDependency, CamelLoader, Capability, +MavenSpec, RuntimeSpec)

@@ -3830,6 +3831,19 @@ Kubernetes meta/v1.Duration + + +extension
+ + +[]MavenArtifact + + + + +

Maven build extensions https://maven.apache.org/guides/mini/guide-using-extensions.html

+ +

PlatformInjectable diff --git a/docs/modules/traits/pages/jvm.adoc b/docs/modules/traits/pages/jvm.adoc index dcf5705c29..e60a5e1e93 100755 --- a/docs/modules/traits/pages/jvm.adoc +++ b/docs/modules/traits/pages/jvm.adoc @@ -47,6 +47,17 @@ The following configuration options are available: | []string | A list of JVM options +| jvm.classpath +| string +| Additional JVM classpath (use `Linux` classpath separator) + |=== // End of autogenerated code - DO NOT EDIT! (configuration) + +== Examples + +* Include an additional classpath to the `Integration`: ++ +[source,console] +$ kamel run -t jvm.classpath=/path/to/my-dependency.jar:/path/to/another-dependency.jar ... diff --git a/e2e/common/traits/files/jvm/Classpath.java b/e2e/common/traits/files/jvm/Classpath.java new file mode 100644 index 0000000000..a405eea9e5 --- /dev/null +++ b/e2e/common/traits/files/jvm/Classpath.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; +import org.apache.camel.example.MyClass; + +public class Classpath extends RouteBuilder { + @Override + public void configure() throws Exception { + from("timer:tick") + .log(MyClass.sayHello()); + } +} \ No newline at end of file diff --git a/e2e/common/traits/files/jvm/sample-1.0.jar b/e2e/common/traits/files/jvm/sample-1.0.jar new file mode 100644 index 0000000000..4996795108 Binary files /dev/null and b/e2e/common/traits/files/jvm/sample-1.0.jar differ diff --git a/e2e/common/traits/jvm_test.go b/e2e/common/traits/jvm_test.go new file mode 100644 index 0000000000..c15c977332 --- /dev/null +++ b/e2e/common/traits/jvm_test.go @@ -0,0 +1,61 @@ +// +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 traits + +import ( + "io/ioutil" + "testing" + + . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" + + v1 "k8s.io/api/core/v1" + + . "github.com/apache/camel-k/e2e/support" + camelv1 "github.com/apache/camel-k/pkg/apis/camel/v1" +) + +func TestJVMTrait(t *testing.T) { + WithNewTestNamespace(t, func(ns string) { + Expect(Kamel("install", "-n", ns).Execute()).To(Succeed()) + + // Store a configmap holding a jar + var cmData = make(map[string][]byte) + // We calculate the expected content + source, err := ioutil.ReadFile("./files/jvm/sample-1.0.jar") + assert.Nil(t, err) + cmData["sample-1.0.jar"] = source + err = NewBinaryConfigmap(ns, "my-deps", cmData) + assert.Nil(t, err) + + t.Run("JVM trait classpath", func(t *testing.T) { + Expect(Kamel("run", "-n", ns, "./files/jvm/Classpath.java", + "--resource", "configmap:my-deps", + "-t", "jvm.classpath=/etc/camel/resources/my-deps/sample-1.0.jar", + ).Execute()).To(Succeed()) + Eventually(IntegrationPodPhase(ns, "classpath"), TestTimeoutMedium).Should(Equal(v1.PodRunning)) + Eventually(IntegrationCondition(ns, "classpath", camelv1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(v1.ConditionTrue)) + Eventually(IntegrationLogs(ns, "classpath"), TestTimeoutShort).Should(ContainSubstring("Hello World!")) + Expect(Kamel("delete", "--all", "-n", ns).Execute()).To(Succeed()) + }) + }) +} diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go index 93a1e093b2..0fc27de6cf 100644 --- a/e2e/support/test_support.go +++ b/e2e/support/test_support.go @@ -674,6 +674,21 @@ func NewPlainTextConfigmap(ns string, name string, data map[string]string) error return TestClient().Create(TestContext, &cm) } +func NewBinaryConfigmap(ns string, name string, data map[string][]byte) error { + cm := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + BinaryData: data, + } + return TestClient().Create(TestContext, &cm) +} + func NewPlainTextSecret(ns string, name string, data map[string]string) error { sec := corev1.Secret{ TypeMeta: metav1.TypeMeta{ diff --git a/examples/README.md b/examples/README.md index c6fe58d19d..03b605f300 100644 --- a/examples/README.md +++ b/examples/README.md @@ -42,4 +42,12 @@ As soon as you will learn the basic stuff, you will like to try the new advanced | Polyglot | Polyglot integration examples | [see examples](./polyglot/)| | Pulsar | Pulsar usage | [see examples](./pulsar/)| | Saga | Saga pattern example | [see examples](./saga/)| -| Tekton | Tekton tutorial | [see examples](./tekton/)| \ No newline at end of file +| Tekton | Tekton tutorial | [see examples](./tekton/)| + +## Traits usage examples + +Traits configuration will be very helpful to fine tune your `Integration`. Here a few examples: + +| Type | Description | Link | +|---|---|---| +| JVM | How to use `JVM` trait| [see examples](./traits/jvm/)| \ No newline at end of file diff --git a/examples/traits/README.md b/examples/traits/README.md new file mode 100644 index 0000000000..6ddb21e2b4 --- /dev/null +++ b/examples/traits/README.md @@ -0,0 +1,3 @@ +# Camel K Traits + +In this section you will find examples about fine tuning your `Integration` using `trait` capability. Have a look at each directory containing example for the available traits. diff --git a/examples/traits/jvm/Classpath.java b/examples/traits/jvm/Classpath.java new file mode 100644 index 0000000000..40d76cd0ba --- /dev/null +++ b/examples/traits/jvm/Classpath.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +// Create a configmap holding a jar in order to simulate the presence of a dependency on the runtime image +// kubectl create configmap my-dep --from-file=sample-1.0.jar + +//kamel run --resource configmap:my-dep -t jvm.classpath=/etc/camel/resources/my-dep/sample-1.0.jar Classpath.java --dev + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.example.MyClass; + +public class Classpath extends RouteBuilder { + @Override + public void configure() throws Exception { + from("timer:tick") + .log(MyClass.sayHello()); + } +} \ No newline at end of file diff --git a/examples/traits/jvm/README.md b/examples/traits/jvm/README.md new file mode 100644 index 0000000000..f7a2896e07 --- /dev/null +++ b/examples/traits/jvm/README.md @@ -0,0 +1,3 @@ +# Camel K JVM Trait + +In this section you will find examples about fine tuning your `Integration` using **JVM** `trait` capability. \ No newline at end of file diff --git a/examples/traits/jvm/sample-1.0.jar b/examples/traits/jvm/sample-1.0.jar new file mode 100644 index 0000000000..4996795108 Binary files /dev/null and b/examples/traits/jvm/sample-1.0.jar differ diff --git a/pkg/trait/jvm.go b/pkg/trait/jvm.go index 44da721e61..1371e03788 100644 --- a/pkg/trait/jvm.go +++ b/pkg/trait/jvm.go @@ -54,6 +54,8 @@ type jvmTrait struct { DebugAddress string `property:"debug-address" json:"debugAddress,omitempty"` // A list of JVM options Options []string `property:"options" json:"options,omitempty"` + // Additional JVM classpath (use `Linux` classpath separator) + Classpath string `property:"classpath" json:"classpath,omitempty"` } func newJvmTrait() Trait { @@ -98,6 +100,9 @@ func (t *jvmTrait) Apply(e *Environment) error { classpath.Add("./resources") classpath.Add(configResourcesMountPath) classpath.Add(resourcesDefaultMountPath) + if t.Classpath != "" { + classpath.Add(strings.Split(t.Classpath, ":")...) + } for _, artifact := range kit.Status.Artifacts { classpath.Add(artifact.Target) diff --git a/pkg/trait/jvm_test.go b/pkg/trait/jvm_test.go index b5681331d9..ebf7909bcc 100644 --- a/pkg/trait/jvm_test.go +++ b/pkg/trait/jvm_test.go @@ -218,6 +218,39 @@ func TestApplyJvmTraitWithExternalKitType(t *testing.T) { assert.Equal(t, "io.quarkus.bootstrap.runner.QuarkusEntryPoint", container.Args[2]) } +func TestApplyJvmTraitWithClasspath(t *testing.T) { + trait, environment := createNominalJvmTest(v1.IntegrationKitTypePlatform) + trait.Classpath = "/path/to/my-dep.jar:/path/to/another/dep.jar" + d := appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: defaultContainerName, + VolumeMounts: []corev1.VolumeMount{ + { + MountPath: "/mount/path", + }, + }, + }, + }, + }, + }, + }, + } + environment.Resources.Add(&d) + err := trait.Apply(environment) + + assert.Nil(t, err) + assert.Equal(t, []string{ + "-cp", + fmt.Sprintf("./resources:%s:%s:/mount/path:%s:%s", configResourcesMountPath, resourcesDefaultMountPath, + "/path/to/another/dep.jar", "/path/to/my-dep.jar"), + "io.quarkus.bootstrap.runner.QuarkusEntryPoint", + }, d.Spec.Template.Spec.Containers[0].Args) +} + func createNominalJvmTest(kitType string) (*jvmTrait, *Environment) { catalog, _ := camel.DefaultCatalog()