Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #1449: add support for modeline options #1455

Merged
merged 6 commits into from
May 11, 2020
Merged
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
3 changes: 2 additions & 1 deletion cmd/kamel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ func main() {
// Cancel ctx as soon as main returns
defer cancel()

rootCmd, err := cmd.NewKamelCommand(ctx)
// Add modeline options to the command
rootCmd, _, err := cmd.NewKamelWithModelineCommand(ctx, os.Args)
exitOnError(err)

err = rootCmd.Execute()
Expand Down
2 changes: 0 additions & 2 deletions docs/modules/ROOT/nav-end.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
* xref:tutorials/tutorials.adoc[Tutorials]
** xref:tutorials/tekton/tekton.adoc[Tekton Pipelines]
* xref:uninstalling.adoc[Uninstalling]
* xref:developers.adoc[Contributing to Camel K]
4 changes: 4 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
*** xref:installation/registry/gcr.adoc[Gcr.io]
* xref:running/running.adoc[Running]
** xref:running/dev-mode.adoc[Dev Mode]
* xref:tutorials/tutorials.adoc[Tutorials]
** xref:tutorials/tekton/tekton.adoc[Tekton Pipelines]
* xref:cli/cli.adoc[CLI]
** xref:cli/modeline.adoc[Modeline]
* xref:configuration/configuration.adoc[Configuration]
** xref:configuration/components.adoc[Components]
** xref:configuration/logging.adoc[Logging]
Expand Down
54 changes: 54 additions & 0 deletions docs/modules/ROOT/pages/cli/cli.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
= Camel K CLI (kamel)

The Camel K command line interface (kamel) is the main entry point for running integrations on a Kubernetes cluster.

Releases of the Camel K CLI are available on:

- Apache Mirrors (official): https://downloads.apache.org/camel/camel-k/
- Github Releases: https://github.com/apache/camel-k/releases
- Homebrew (Mac and Linux): https://formulae.brew.sh/formula/kamel

== Available Commands

Some of the most used commands are:

.Useful Commands
|===
|Name |Description |Example

|help
|Obtain the full list of available commands
|`kamel help`

|init
|Initialize empty Camel K files
|`kamel init Routes.java`

|run
|Run an integration on Kubernetes
|`kamel run Routes.java`

|get
|Get integrations deployed on Kubernetes
|`kamel get`

|describe
|Get detailed information on a resource
|`kamel describe integration routes`

|log
|Print the logs of a running integration
|`kamel log routes`

|delete
|Delete integrations deployed on Kubernetes
|`kamel delete routes`

|===

The list above is not the full list of available commands. You can run `kamel help` to obtain the full list.

== Modeline

Some command options in the CLI can be also specified as modeline in the source file, take a look at the xref:cli/modeline.adoc[Modeline section]
for more information.
98 changes: 98 additions & 0 deletions docs/modules/ROOT/pages/cli/modeline.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
= Camel K Modeline

Integration files can contain modeline hooks that allow to customize the way integrations are executed via command line.

For example, take the following integration file:

.Hello.java
----
// camel-k: dependency=mvn:org.my/application:1.0 // <1>

import org.apache.camel.builder.RouteBuilder;

public class Hello extends RouteBuilder {
@Override
public void configure() throws Exception {

from("timer:java?period=1000")
.bean(org.my.BusinessLogic) // <2>
.log("${body}");

}
}
----
<1> Modeline import of Maven library
<2> Usage of a business logic class from the external library

When the integration code above is executed using the `kamel run` CLI command, the modeline options declared in the file are appended to
the list of arguments that are passed to the command.

The `kamel` CLI will alert you, printing the full command in the shell:

----
$ kamel run Hello.java
Modeline options have been loaded from source files
Full command: kamel run Hello.java --dependency mvn:org.my/application:1.0
...
----

Multiple options, even of the same type, can be specified for an integration. For example
the following modeline options make sure that the integration runs on the Quarkus runtime and enable the 3scale exposure.

.QuarkusRest.java
----
// camel-k: trait=quarkus.enabled=true trait=3scale.enabled=true // <1>

import org.apache.camel.builder.RouteBuilder;

public class QuarkusRest extends RouteBuilder {
@Override
public void configure() throws Exception {

rest().get("/")
.route()
.setBody().constant("Hello");

}
}
----
<1> Enable both the Quarkus and 3scale traits, to run the integration on Quarkus and expose the routes via 3scale

All options that are available for the `kamel run` command can be specified as modeline options.
The following is a partial list of useful options:

.Useful Modeline Options
|===
|Option | Description

|dependency
|An external library that should be included. E.g. for Maven dependencies "dependency=mvn:org.my/app:1.0"

|env
|Set an environment variable in the integration container. E.g "env=MY_VAR=my-value"

|label
|Add a label to the integration. E.g. "label=my.company=hello"

|name
|The integration name

|open-api
|Add an OpenAPI v2 spec (file path)

|profile
|Trait profile used for deployment

|property
|Add a camel property

|property-file
|Bind a property file to the integration. E.g. "property-file=integration.properties"

|resource
|Add a resource

|trait
|Configure a trait. E.g. "trait=service.enabled=false"

|===
6 changes: 6 additions & 0 deletions e2e/common/offline_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func TestKamelHelpTraitWorksOffline(t *testing.T) {
assert.Nil(t, traitCmd.Execute())
}

func TestKamelHelpOptionWorksOffline(t *testing.T) {
traitCmd := Kamel("run", "Xxx.java", "--help")
traitCmd.SetOut(ioutil.Discard)
assert.Nil(t, traitCmd.Execute())
}

func TestKamelCompletionWorksOffline(t *testing.T) {
bashCmd := Kamel("completion", "bash", "--config", "non-existent-kubeconfig-file")
bashCmd.SetOut(ioutil.Discard)
Expand Down
148 changes: 148 additions & 0 deletions pkg/cmd/modeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package cmd

import (
"context"
"fmt"
"path"

"github.com/apache/camel-k/pkg/util/modeline"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"path/filepath"
)

const (
runCmdName = "run"
runCmdSourcesArgs = "source"
)

var (
nonRunOptions = map[string]bool{
"language": true, // language is a marker modeline option for other tools
}

// file options must be considered relative to the source files they belong to
fileOptions = map[string]bool{
"source": true,
"resource": true,
"config": true,
"open-api": true,
"property-file": true,
}
)

func NewKamelWithModelineCommand(ctx context.Context, osArgs []string) (*cobra.Command, []string, error) {
processed := make(map[string]bool)
originalFlags := osArgs[1:]
rootCmd, flags, err := createKamelWithModelineCommand(ctx, append([]string(nil), originalFlags...), processed)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return rootCmd, flags, err
}
if len(originalFlags) != len(flags) {
// Give a feedback about the actual command that is run
fmt.Fprintln(rootCmd.OutOrStdout(), "Modeline options have been loaded from source files")
fmt.Fprint(rootCmd.OutOrStdout(), "Full command: kamel ")
for _, a := range flags {
fmt.Fprintf(rootCmd.OutOrStdout(), "%s ", a)
}
fmt.Fprintln(rootCmd.OutOrStdout())
}
return rootCmd, flags, nil
}

func createKamelWithModelineCommand(ctx context.Context, args []string, processedFiles map[string]bool) (*cobra.Command, []string, error) {
rootCmd, err := NewKamelCommand(ctx)
if err != nil {
return nil, nil, err
}

target, flags, err := rootCmd.Find(args)
if err != nil {
return nil, nil, err
}

if target.Name() != runCmdName {
return rootCmd, args, nil
}

err = target.ParseFlags(flags)
if err == pflag.ErrHelp {
return rootCmd, args, nil
} else if err != nil {
return nil, nil, err
}

fg := target.Flags()

sources, err := fg.GetStringArray(runCmdSourcesArgs)
if err != nil {
return nil, nil, err
}

var files = append([]string(nil), fg.Args()...)
files = append(files, sources...)

opts := make([]modeline.Option, 0)
for _, f := range files {
if processedFiles[f] {
continue
}
baseDir := filepath.Dir(f)
content, err := loadData(f, false)
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot read file %s", f)
}
ops, err := modeline.Parse(f, content)
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot process file %s", f)
}
for i, o := range ops {
if fileOptions[o.Name] && !isRemoteHTTPFile(f) {
refPath := o.Value
if !filepath.IsAbs(refPath) {
full := path.Join(baseDir, refPath)
o.Value = full
ops[i] = o
}
}
}
opts = append(opts, ops...)
}
// filter out in place non-run options
nOpts := 0
for _, o := range opts {
if !nonRunOptions[o.Name] {
opts[nOpts] = o
nOpts++
}
}
opts = opts[:nOpts]

// No new options, returning a new command with computed args
if len(opts) == 0 {
// Recreating the command as it's dirty
rootCmd, err = NewKamelCommand(ctx)
if err != nil {
return nil, nil, err
}
rootCmd.SetArgs(args)
return rootCmd, args, nil
}

// New options added, recomputing
for _, f := range files {
processedFiles[f] = true
}
for _, o := range opts {
prefix := "-"
if len(o.Name) > 1 {
prefix = "--"
}
args = append(args, fmt.Sprintf("%s%s", prefix, o.Name))
args = append(args, o.Value)
}

return createKamelWithModelineCommand(ctx, args, processedFiles)
}
Loading