Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion docs/modules/ROOT/pages/running/running-cli.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,38 @@ status: {}
```
This can be saved for future processing (ie, stored to a GIT repository and later deployed to a cluster via some GitOps deployment strategy). Consider that any **modeline** option will be translated accordingly.

Dry run is also the easiest way to customize per-source native build behavior. For example, if you are building a native executable and one of the source files should be embedded as a classpath resource instead of being interpreted as a Camel route, you can:

1. Generate the Integration manifest with `kamel run ... -o yaml`
2. Edit the relevant `spec.sources[]` entry
3. Set `nativeImage: resource`
4. Apply the resulting manifest with `kubectl apply -f`

For example:

[source,console]
----
kamel run Hello.java app-config.yaml -t quarkus.build-mode=native -o yaml
----

Then edit the generated Integration so the resource entry looks like:

[source,yaml]
----
spec:
sources:
- name: Hello.java
content: |
...
- name: app-config.yaml
nativeImage: resource
content: |
greeting: hello
audience: native
----

This is particularly useful for files such as `.yaml` that may otherwise be inferred as Camel DSL routes.

[[modeline]]
== Camel K Modeline

Expand Down Expand Up @@ -226,4 +258,4 @@ kamel run \
https://gist.githubusercontent.com/${user-id}/${gist-id}/raw/${...}/routes.yaml
----

NOTE: GitHub applies rate limiting to its APIs and as Authenticated requests get a higher rate limit, the `kamel` honour the env var GITHUB_TOKEN and if it is found, then it is used for GitHub authentication.
NOTE: GitHub applies rate limiting to its APIs and as Authenticated requests get a higher rate limit, the `kamel` honour the env var GITHUB_TOKEN and if it is found, then it is used for GitHub authentication.
52 changes: 52 additions & 0 deletions docs/modules/ROOT/pages/running/running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,58 @@ spec:

You can see the specification is a lot neater, so, try choosing Yaml DSL whenever it's possible.

== Native builds and classpath resources

When you build a native executable, Camel K has to decide whether each entry in `spec.sources` is:

- a Camel route that must be loaded by Camel
- a classpath resource that must be embedded into the native executable

By default Camel K infers this from the source language or file extension. You can override that behavior explicitly with `spec.sources[].nativeImage`.

This is useful when a file has a known Camel DSL extension, such as `.yaml`, but it is actually application data and not a route.

[source,yaml]
----
apiVersion: camel.apache.org/v1
kind: Integration
metadata:
name: native-resource-example
spec:
sources:
- name: Hello.java
content: |
import org.apache.camel.builder.RouteBuilder;

public class Hello extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:tick?period=3000")
.setBody().constant("Hello from Camel K")
.log("${body}");
}
}
- name: files/app-config.yaml
nativeImage: resource
content: |
greeting: hello
audience: native
traits:
quarkus:
buildMode:
- native
----

The `nativeImage` field accepts:

- `auto`: Camel K infers the behavior. This is the default.
- `route`: force the source to be treated as a Camel route.
- `resource`: force the source to be packaged as a classpath resource.

For example, if you really want a YAML file to stay a route in a native build, omit the field or set `nativeImage: route`. If you want the same kind of file to be embedded as application data, set `nativeImage: resource`.

NOTE: `spec.sources[].nativeImage` is about files stored in the Integration itself. It does not replace runtime-mounted ConfigMaps or Secrets configured with xref:traits:mount.adoc[mount trait].

== Runtime provider

Camel K was originally equipped with a dedicated runtime known as Camel K Runtime. This is a lightweight layer on top of Camel Quarkus. However, you can directly run plain regular Camel Quarkus runtime applications as well. You will learn the concept of traits later on. For now, just be aware that you can run any Integration setting the plain Quarkus runtime using `camel` trait configuration. Here an example of how that would be:
Expand Down
59 changes: 59 additions & 0 deletions docs/modules/traits/pages/quarkus.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,62 @@ NOTE: the variable names are "snake case" if you're using in `kamel` CLI, for ex


// End of autogenerated code - DO NOT EDIT! (configuration)

== Native source inclusion

When Camel K builds a native executable, it needs to decide how each entry in `Integration.spec.sources` should be materialized.

By default Camel K uses the source file name and language inference:

- Camel DSL sources are treated as routes
- non-DSL files are treated as classpath resources

You can now override this behavior explicitly on each `spec.sources[]` entry with the `nativeImage` field:

[cols="1m,4a"]
|===
|Value | Description

|auto
|Use Camel K default inference. This is the default when the field is omitted.

|route
|Force the source to be treated as a Camel route during native build.

|resource
|Force the source to be packaged as a classpath resource during native build.
|===

This is especially useful when a file uses a known Camel DSL extension such as `.yaml`, but the file is application data and must be available on the classpath of the native executable.

[source,yaml]
----
apiVersion: camel.apache.org/v1
kind: Integration
metadata:
name: native-resource-example
spec:
sources:
- name: routes/route.yaml
content: |
- from:
uri: "timer:tick"
steps:
- log: "Hello from Camel K"
- name: files/app-config.yaml
nativeImage: resource
content: |
greeting: hello
audience: native
traits:
quarkus:
buildMode:
- native
----

In the example above:

- `routes/route.yaml` is still treated as a Camel route
- `files/app-config.yaml` is packaged into the native image as a classpath resource

NOTE: `spec.sources[].nativeImage` only affects how Camel K packages entries from `spec.sources` during a native build. It is different from `mount.resources`, which mounts ConfigMaps or Secrets into the running Pod at runtime.
16 changes: 16 additions & 0 deletions pkg/apis/camel/v1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ type SourceSpec struct {

// specify which is the language (Camel DSL) used to interpret this source code
Language Language `json:"language,omitempty"`
// Controls how the source is included when building a native executable.
// `auto` uses Camel K inference, `route` forces route inclusion, `resource` forces classpath resource inclusion.
NativeImage NativeImageSourceType `json:"nativeImage,omitempty"`
// Loader is an optional id of the org.apache.camel.k.RoutesLoader that will
// interpret this source at runtime
Loader string `json:"loader,omitempty"`
Expand Down Expand Up @@ -511,6 +514,19 @@ const (
SourceTypeErrorHandler SourceType = "errorHandler"
)

// NativeImageSourceType determines how a source should be materialized in a native build.
// +kubebuilder:validation:Enum=auto;route;resource
type NativeImageSourceType string

const (
// NativeImageSourceTypeAuto lets Camel K infer whether the source is a route or a resource.
NativeImageSourceTypeAuto NativeImageSourceType = "auto"
// NativeImageSourceTypeRoute forces the source to be treated as a Camel route during native build.
NativeImageSourceTypeRoute NativeImageSourceType = "route"
// NativeImageSourceTypeResource forces the source to be treated as a classpath resource during native build.
NativeImageSourceTypeResource NativeImageSourceType = "resource"
)

// DataSpec represents the way the source is materialized in the running `Pod`.
type DataSpec struct {
// the name of the specification
Expand Down
29 changes: 29 additions & 0 deletions pkg/apis/camel/v1/common_types_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,35 @@ func (s *SourceSpec) InferLanguage() Language {

return ""
}
func (s *SourceSpec) NativeImageSourceType() NativeImageSourceType {
if s.NativeImage == "" {
return NativeImageSourceTypeAuto
}

return s.NativeImage
}

func (s *SourceSpec) NativeImageAsResource() bool {
switch s.NativeImageSourceType() {
case NativeImageSourceTypeResource:
return true
case NativeImageSourceTypeRoute:
return false
default:
return s.InferLanguage() == ""
}
}

func (s *SourceSpec) NativeImageAsRoute() bool {
switch s.NativeImageSourceType() {
case NativeImageSourceTypeRoute:
return true
case NativeImageSourceTypeResource:
return false
default:
return s.InferLanguage() != ""
}
}

// Validate checks if the strategy is supported.
func (b BuildStrategy) Validate() error {
Expand Down
56 changes: 56 additions & 0 deletions pkg/apis/camel/v1/integration_types_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,62 @@ func TestLanguageAlreadySet(t *testing.T) {
assert.Equal(t, LanguageJavaSource, code.InferLanguage())
}

func TestNativeImageSourceTypeDefaultsToAuto(t *testing.T) {
code := SourceSpec{
DataSpec: DataSpec{
Name: "request.yaml",
},
}

assert.Equal(t, NativeImageSourceTypeAuto, code.NativeImageSourceType())
assert.True(t, code.NativeImageAsRoute())
assert.False(t, code.NativeImageAsResource())
}

func TestNativeImageSourceTypeCanForceResource(t *testing.T) {
code := SourceSpec{
DataSpec: DataSpec{
Name: "request.yaml",
},
NativeImage: NativeImageSourceTypeResource,
}

assert.Equal(t, NativeImageSourceTypeResource, code.NativeImageSourceType())
assert.False(t, code.NativeImageAsRoute())
assert.True(t, code.NativeImageAsResource())
}

func TestNativeImageSourceTypeCanForceRoute(t *testing.T) {
code := SourceSpec{
DataSpec: DataSpec{
Name: "notes.txt",
},
NativeImage: NativeImageSourceTypeRoute,
}

assert.Equal(t, NativeImageSourceTypeRoute, code.NativeImageSourceType())
assert.True(t, code.NativeImageAsRoute())
assert.False(t, code.NativeImageAsResource())
}

func TestNativeImageSourceTypeAutoAndEmptyAreEquivalent(t *testing.T) {
implicit := SourceSpec{
DataSpec: DataSpec{
Name: "request.yaml",
},
}
explicit := SourceSpec{
DataSpec: DataSpec{
Name: "request.yaml",
},
NativeImage: NativeImageSourceTypeAuto,
}

assert.Equal(t, implicit.NativeImageSourceType(), explicit.NativeImageSourceType())
assert.Equal(t, implicit.NativeImageAsRoute(), explicit.NativeImageAsRoute())
assert.Equal(t, implicit.NativeImageAsResource(), explicit.NativeImageAsResource())
}

func TestAddDependency(t *testing.T) {
integration := IntegrationSpec{}
integration.AddDependency("camel:file")
Expand Down
28 changes: 27 additions & 1 deletion pkg/builder/quarkus.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,24 @@
}

sourceList := ""
resourceList := make([]string, 0)
for _, source := range ctx.Build.Sources {
if source.NativeImageAsResource() {
resourcePath := filepath.Join(ctx.Path, "maven", "src", "main", "resources", source.Name)
if err := os.MkdirAll(filepath.Dir(resourcePath), os.ModePerm); err != nil {
return fmt.Errorf("failure while creating resource folder: %w", err)
}
if err := os.WriteFile(
resourcePath,
[]byte(source.Content),
projectModePerm,
); err != nil {
return fmt.Errorf("failure while writing %s: %w", source.Name, err)
}
resourceList = append(resourceList, filepath.ToSlash(source.Name))
continue

Check failure on line 96 in pkg/builder/quarkus.go

View workflow job for this annotation

GitHub Actions / validate

continue with no blank line before (nlreturn)
}

if sourceList != "" {
sourceList += ","
}
Expand All @@ -102,8 +119,17 @@
return fmt.Errorf("failure while writing the configuration application.properties: %w", err)
}
}

if len(resourceList) > 0 {
if ctx.Build.Maven.Properties == nil {
ctx.Build.Maven.Properties = make(map[string]string)
}
resourceIncludes := strings.Join(resourceList, ",")
if existing := ctx.Build.Maven.Properties["quarkus.native.resources.includes"]; existing != "" {
resourceIncludes = existing + "," + resourceIncludes
}
ctx.Build.Maven.Properties["quarkus.native.resources.includes"] = resourceIncludes
}
return nil

Check failure on line 132 in pkg/builder/quarkus.go

View workflow job for this annotation

GitHub Actions / validate

return with no blank line before (nlreturn)
}

func loadCamelQuarkusCatalog(ctx *builderContext) error {
Expand Down
40 changes: 40 additions & 0 deletions pkg/builder/quarkus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,46 @@ func TestGenerateQuarkusProjectWithNativeSources(t *testing.T) {
require.NoError(t, err)
}

func TestGenerateQuarkusProjectWithNativeResources(t *testing.T) {
tmpDir := t.TempDir()
defaultCatalog, err := camel.DefaultCatalog()
require.NoError(t, err)

builderContext := builderContext{
C: context.TODO(),
Path: tmpDir,
Namespace: "test",
Build: v1.BuilderTask{
Runtime: defaultCatalog.Runtime,
Maven: v1.MavenBuildSpec{
MavenSpec: v1.MavenSpec{},
},
Sources: []v1.SourceSpec{
v1.NewSourceSpec("Test.java", "bogus, irrelevant for test", v1.LanguageJavaSource),
{
DataSpec: v1.DataSpec{
Name: "resources/my-resource.yaml",
Content: "hello: from-resource",
},
NativeImage: v1.NativeImageSourceTypeResource,
},
},
},
}

err = prepareProjectWithSources(&builderContext)
require.NoError(t, err)

assert.Equal(t, "resources/my-resource.yaml", builderContext.Build.Maven.Properties["quarkus.native.resources.includes"])

materializedResource, err := os.ReadFile(filepath.Join(tmpDir, "maven", "src", "main", "resources", "resources", "my-resource.yaml"))
require.NoError(t, err)
assert.Equal(t, "hello: from-resource", string(materializedResource))

_, err = os.Stat(filepath.Join(tmpDir, "maven", "src", "main", "resources", "routes", "resources", "my-resource.yaml"))
assert.Error(t, err)
}

func TestBuildQuarkusRunner(t *testing.T) {
tmpDir := t.TempDir()
defaultCatalog, err := camel.DefaultCatalog()
Expand Down
Loading
Loading