Repository: apache/camel-k
Pull request #1924 (2021-01-20 02:47:58)

Fix modeline for inspect.
<!-- Description -->


Enable modeline options for inspect command.

<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
Enable modeline support for inspect.
```


doru1004: > I think it would be useful to provide tests for the various additional use cases:
> 
> * modeline-specified dependency added using mvn notation
> * modeline-specified dependency added using "simple" notation for a camel component
> * modeline-specified dependency added using jitpack notation
> * several modeline-specified dependencies written on several lines

I have added some tests. The value of the dependency does not matter at this point. What we were missing for modeline is the addition of these dependencies when inspect was used.

Patch:
diff --git a/examples/kamelets/timer-source-log-sink/timer-source.kamelet.yaml b/examples/kamelets/timer-source-log-sink/timer-source.kamelet.yaml
index 47a63931b..20e1d662f 100644
--- a/examples/kamelets/timer-source-log-sink/timer-source.kamelet.yaml
+++ b/examples/kamelets/timer-source-log-sink/timer-source.kamelet.yaml
@@ -47,7 +47,7 @@ spec:
     from:
       uri: timer:tick
       parameters:
-        period: "#property:period"
+        period: "{{period}}"
       steps:
       - set-body:
           constant: "{{message}}"
diff --git a/examples/kamelets/timer-source/timer-source.kamelet.yaml b/examples/kamelets/timer-source/timer-source.kamelet.yaml
index 4fe90ab0b..f837fb5d8 100644
--- a/examples/kamelets/timer-source/timer-source.kamelet.yaml
+++ b/examples/kamelets/timer-source/timer-source.kamelet.yaml
@@ -47,7 +47,7 @@ spec:
     from:
       uri: timer:tick
       parameters:
-        period: "#property:period"
+        period: "{{period}}"
       steps:
       - set-body:
           constant: "{{message}}"
diff --git a/examples/saga/Flight.java b/examples/saga/Flight.java
index 24d8e0dc9..5aacbd92d 100644
--- a/examples/saga/Flight.java
+++ b/examples/saga/Flight.java
@@ -37,7 +37,8 @@ public class Flight extends RouteBuilder {
 				.option("id", header("id"))
 				.compensation("direct:cancelPurchase")
 			.log("Buying flight #${header.id}")
-			.to("undertow:http://payment/api/pay?bridgeEndpoint=true&type=flight")
+			.removeHeaders("CamelHttp.*")
+			.to("http://payment/api/pay?httpMethod=POST&type=flight")
 			.log("Payment for flight #${header.id} done");
 
 		from("direct:cancelPurchase")
diff --git a/examples/saga/Saga.java b/examples/saga/Saga.java
index ca6a38624..ccaa78ad2 100644
--- a/examples/saga/Saga.java
+++ b/examples/saga/Saga.java
@@ -35,8 +35,8 @@ public class Saga extends RouteBuilder {
 			.setHeader("id", header(Exchange.TIMER_COUNTER))
 			.setHeader(Exchange.HTTP_METHOD, constant("POST"))
 			.log("Executing saga #${header.id}")
-			.to("undertow:http://train/api/train/buy/seat")
-			.to("undertow:http://flight/api/flight/buy");
+			.to("http://train/api/train/buy/seat?bridgeEndpoint=true")
+			.to("http://flight/api/flight/buy?bridgeEndpoint=true");
 
 	}
 }
diff --git a/examples/saga/Train.java b/examples/saga/Train.java
index 429df5fe7..b46704d05 100644
--- a/examples/saga/Train.java
+++ b/examples/saga/Train.java
@@ -37,7 +37,8 @@ public class Train extends RouteBuilder {
                         .option("id", header("id"))
                         .compensation("direct:cancelPurchase")
                     .log("Buying train seat #${header.id}")
-                    .to("undertow:http://payment/api/pay?bridgeEndpoint=true&type=train")
+                    .removeHeaders("CamelHttp.*")
+                    .to("http://payment/api/pay?httpMethod=POST&type=train")
                     .log("Payment for train #${header.id} done");
 
                from("direct:cancelPurchase")
diff --git a/helm/camel-k/templates/NOTES.txt b/helm/camel-k/templates/NOTES.txt
index 39994011e..32c70c08a 100644
--- a/helm/camel-k/templates/NOTES.txt
+++ b/helm/camel-k/templates/NOTES.txt
@@ -1,7 +1,7 @@
 {{/*
 Constraints
 */}}
-{{- if (not .Values.platform.cluster) or (.Values.platform.cluster ne "OpenShift") }}
+{{- if or (not .Values.platform.cluster) (ne .Values.platform.cluster "OpenShift") }}
 {{ required "Field \"platform.build.registry.address\" is required when not running on OpenShift (set \"platform.cluster=OpenShift\" if you're using OpenShift instead)!" .Values.platform.build.registry.address | substr 0 0 }}
 {{- end }}
 
diff --git a/helm/camel-k/values.yaml b/helm/camel-k/values.yaml
index 0167685ee..a0da4d0b7 100644
--- a/helm/camel-k/values.yaml
+++ b/helm/camel-k/values.yaml
@@ -28,3 +28,4 @@ operator:
 platform:
   build:
     registry: {}
+  cluster: ""
diff --git a/pkg/builder/runtime/quarkus.go b/pkg/builder/runtime/quarkus.go
index a98faba30..53c1c2239 100644
--- a/pkg/builder/runtime/quarkus.go
+++ b/pkg/builder/runtime/quarkus.go
@@ -83,7 +83,7 @@ func GenerateQuarkusProjectCommon(camelQuarkusVersion string, runtimeVersion str
 	// this to happen as routes are loaded at runtime and looking for
 	// routes at build time may try to load camel-k-runtime routes builder
 	// proxies which in some case may fail
-	p.Properties["quarkus.camel.main.routes-discovery.enabled"] = "false"
+	p.Properties["quarkus.camel.routes-discovery.enabled"] = "false"
 
 	// disable quarkus banner ...
 	p.Properties["quarkus.banner.enabled"] = "false"
diff --git a/pkg/cmd/inspect.go b/pkg/cmd/inspect.go
index 6979502f6..3687879b7 100644
--- a/pkg/cmd/inspect.go
+++ b/pkg/cmd/inspect.go
@@ -56,7 +56,7 @@ will be generated by calling Maven and then printed in the selected output forma
 		},
 	}
 
-	cmd.Flags().Bool("all-dependencies", false, "Compute transitive dependencies and move them to directory pointed to by the --dependencies-directory flag.")
+	cmd.Flags().Bool("all-dependencies", false, "Enable computation of transitive dependencies.")
 	cmd.Flags().StringArrayP("dependency", "d", nil, additionalDependencyUsageMessage)
 	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
 	cmd.Flags().StringArray("maven-repository", nil, "Use a maven repository")
diff --git a/pkg/cmd/modeline.go b/pkg/cmd/modeline.go
index ce10b40df..99711e55d 100644
--- a/pkg/cmd/modeline.go
+++ b/pkg/cmd/modeline.go
@@ -36,6 +36,7 @@ const (
 	createCmdName     = "create"
 	localCmdName      = "local"
 	runCmdSourcesArgs = "source"
+	inspectCmdName    = "inspect"
 )
 
 var (
@@ -90,8 +91,9 @@ func createKamelWithModelineCommand(ctx context.Context, args []string) (*cobra.
 	}
 
 	isLocalCreate := target.Name() == createCmdName && target.Parent().Name() == localCmdName
+	isInspect := target.Name() == inspectCmdName
 
-	if target.Name() != runCmdName && !isLocalCreate {
+	if target.Name() != runCmdName && !isLocalCreate && !isInspect {
 		return rootCmd, args, nil
 	}
 
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
index 183a7a8e5..85b7607bb 100644
--- a/pkg/cmd/root.go
+++ b/pkg/cmd/root.go
@@ -19,6 +19,7 @@ package cmd
 
 import (
 	"context"
+	"fmt"
 	"os"
 	"strings"
 
@@ -28,6 +29,7 @@ import (
 
 	"github.com/apache/camel-k/pkg/client"
 	camelv1 "github.com/apache/camel-k/pkg/client/camel/clientset/versioned/typed/camel/v1"
+	"github.com/apache/camel-k/pkg/util/defaults"
 )
 
 const kamelCommandLongDescription = `Apache Camel K is a lightweight integration platform, born on Kubernetes, with serverless
@@ -127,7 +129,7 @@ func kamelPostAddCommandInit(cmd *cobra.Command) error {
 
 func addKamelSubcommands(cmd *cobra.Command, options *RootCmdOptions) {
 	cmd.AddCommand(newCmdCompletion(cmd))
-	cmd.AddCommand(newCmdVersion())
+	cmd.AddCommand(cmdOnly(newCmdVersion(options)))
 	cmd.AddCommand(cmdOnly(newCmdRun(options)))
 	cmd.AddCommand(cmdOnly(newCmdGet(options)))
 	cmd.AddCommand(cmdOnly(newCmdDelete(options)))
@@ -168,24 +170,38 @@ func addHelpSubCommands(cmd *cobra.Command, options *RootCmdOptions) error {
 }
 
 func (command *RootCmdOptions) preRun(cmd *cobra.Command, _ []string) error {
-	if command.Namespace == "" && !isOfflineCommand(cmd) {
-		var current string
+	if !isOfflineCommand(cmd) {
 		client, err := command.GetCmdClient()
 		if err != nil {
 			return errors.Wrap(err, "cannot get command client")
 		}
-		current, err = client.GetCurrentNamespace(command.KubeConfig)
-		if err != nil {
-			return errors.Wrap(err, "cannot get current namespace")
-		}
-		err = cmd.Flag("namespace").Value.Set(current)
-		if err != nil {
-			return err
+		if command.Namespace == "" {
+			current, err := client.GetCurrentNamespace(command.KubeConfig)
+			if err != nil {
+				return errors.Wrap(err, "cannot get current namespace")
+			}
+			err = cmd.Flag("namespace").Value.Set(current)
+			if err != nil {
+				return err
+			}
 		}
+		checkAndShowCompatibilityWarning(command.Context, client, command.Namespace)
 	}
+
 	return nil
 }
 
+func checkAndShowCompatibilityWarning(ctx context.Context, cli client.Client, namespace string) {
+	operatorVersion, err := operatorVersion(ctx, cli, namespace)
+	if err != nil {
+		fmt.Printf("No Integration Platform available in %s namespace\n", namespace)
+	} else {
+		if !compatibleVersions(operatorVersion, defaults.Version) {
+			fmt.Printf("Warning: you're using Camel K %s client against a %s cluster operator\n\n", defaults.Version, operatorVersion)
+		}
+	}
+}
+
 // GetCmdClient returns the client that can be used from command line tools
 func (command *RootCmdOptions) GetCmdClient() (client.Client, error) {
 	// Get the pre-computed client
diff --git a/pkg/cmd/util_containerization.go b/pkg/cmd/util_containerization.go
index dcbd97a8a..f633a0eef 100644
--- a/pkg/cmd/util_containerization.go
+++ b/pkg/cmd/util_containerization.go
@@ -87,7 +87,7 @@ func setDockerEnvVars(envVars []string) {
 	}
 }
 
-func createAndBuildBaseImage(ctx context.Context, containerRegistry string) error {
+func createAndBuildBaseImage(ctx context.Context) error {
 	// Create the base image Docker file.
 	err := docker.CreateBaseImageDockerFile()
 	if err != nil {
@@ -117,6 +117,9 @@ func createAndBuildIntegrationImage(ctx context.Context, containerRegistry strin
 	}
 
 	docker.RegistryName = containerRegistry
+
+	// If we build a normal image, i.e. not the base image, we need to parse
+	// the location where images will be pushed.
 	if !justBaseImage {
 		registryName, err := docker.ExtractRegistryName(image)
 		if err != nil {
@@ -127,7 +130,7 @@ func createAndBuildIntegrationImage(ctx context.Context, containerRegistry strin
 	}
 
 	// Create the Dockerfile and build the base image.
-	err := createAndBuildBaseImage(ctx, containerRegistry)
+	err := createAndBuildBaseImage(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go
index 1097eedd9..6ec8174e2 100644
--- a/pkg/cmd/version.go
+++ b/pkg/cmd/version.go
@@ -18,30 +18,108 @@ limitations under the License.
 package cmd
 
 import (
+	"context"
 	"fmt"
 
+	"github.com/Masterminds/semver"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/client"
 	"github.com/apache/camel-k/pkg/util/defaults"
-
 	"github.com/spf13/cobra"
+	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 // VersionVariant may be overridden at build time
 var VersionVariant = ""
 
-func newCmdVersion() *cobra.Command {
-	return &cobra.Command{
-		Use:   "version",
-		Short: "Display client version",
-		Long:  `Display Camel K client version.`,
-		Run: func(_ *cobra.Command, _ []string) {
-			if VersionVariant != "" {
-				fmt.Printf("Camel K Client %s %s\n", VersionVariant, defaults.Version)
-			} else {
-				fmt.Printf("Camel K Client %s\n", defaults.Version)
-			}
-		},
-		Annotations: map[string]string{
-			offlineCommandLabel: "true",
-		},
+func newCmdVersion(rootCmdOptions *RootCmdOptions) (*cobra.Command, *versionCmdOptions) {
+	options := versionCmdOptions{
+		RootCmdOptions: rootCmdOptions,
+	}
+
+	cmd := cobra.Command{
+		Use:               "version",
+		Short:             "Display client version",
+		Long:              `Display Camel K client version.`,
+		PersistentPreRunE: decode(&options),
+		PreRunE:           options.preRunE,
+		RunE:              options.run,
+		Annotations:       make(map[string]string),
+	}
+
+	cmd.Flags().Bool("operator", false, "Display Operator version")
+
+	return &cmd, &options
+}
+
+type versionCmdOptions struct {
+	*RootCmdOptions
+	Operator bool `mapstructure:"operator"`
+}
+
+func (o *versionCmdOptions) preRunE(cmd *cobra.Command, args []string) error {
+	if !o.Operator {
+		// let the command to work in offline mode
+		cmd.Annotations[offlineCommandLabel] = "true"
+	}
+	return o.RootCmdOptions.preRun(cmd, args)
+}
+
+func (o *versionCmdOptions) run(cmd *cobra.Command, _ []string) error {
+	if o.Operator {
+		client, err := o.GetCmdClient()
+		if err != nil {
+			return err
+		}
+		displayOperatorVersion(o.Context, client, o.Namespace)
+	} else {
+		displayClientVersion()
+	}
+	return nil
+}
+
+func displayClientVersion() {
+	if VersionVariant != "" {
+		fmt.Printf("Camel K Client %s %s\n", VersionVariant, defaults.Version)
+	} else {
+		fmt.Printf("Camel K Client %s\n", defaults.Version)
+	}
+}
+
+func displayOperatorVersion(ctx context.Context, c client.Client, namespace string) {
+	operatorVersion, err := operatorVersion(ctx, c, namespace)
+	if err == nil {
+		fmt.Printf("Camel K Operator %s\n", operatorVersion)
+	} else {
+		fmt.Printf("Error while looking for camel-k operator in namespace %s (%s)\n", namespace, err)
+	}
+}
+
+func operatorVersion(ctx context.Context, c client.Client, namespace string) (string, error) {
+	platform := v1.NewIntegrationPlatform(namespace, "camel-k")
+	platformKey := k8sclient.ObjectKey{
+		Namespace: namespace,
+		Name:      "camel-k",
+	}
+
+	if err := c.Get(ctx, platformKey, &platform); err == nil {
+		return platform.Status.Version, nil
+	} else {
+		return "", err
+	}
+}
+
+func compatibleVersions(aVersion, bVersion string) bool {
+	v1, err := semver.NewVersion(aVersion)
+	if err != nil {
+		fmt.Printf("Could not parse %s (error: %s)\n", v1, err)
+		return false
+	}
+	v2, err := semver.NewVersion(bVersion)
+	if err != nil {
+		fmt.Printf("Could not parse %s (error: %s)\n", v2, err)
+		return false
 	}
+	// We consider compatible when major and minor are equals
+	return v1.Major() == v2.Major() && v1.Minor() == v2.Minor()
 }
diff --git a/pkg/util/docker/docker.go b/pkg/util/docker/docker.go
index 228332dcb..812d8f110 100644
--- a/pkg/util/docker/docker.go
+++ b/pkg/util/docker/docker.go
@@ -146,14 +146,13 @@ func ExtractRegistryName(image string) (string, error) {
 
 	// There must be at least two components in the path:
 	//  - docker.io/registry/imageName
-	//  - registry/imageName
+	//  - localhost:5000/<dir>/imageName
+	//  - localhost:5000/<dir>/.../<dir>/imageName
+	//  - localhost:5000/imageName
 	if len(pathComponents) < 2 {
-		return "", errors.New("image path is too short, usage: docker.io/registry/imageName or registry/imageName")
+		return "", errors.New("image path is too short, usage: registry/imageName or registry/*/imageName")
 	}
 
-	// Check if path starts with docker.io if not, add it.
-	if pathComponents[0] == "docker.io" {
-		return strings.Join(pathComponents[0:2], containerFileSeparator), nil
-	}
-	return "docker.io" + containerFileSeparator + pathComponents[1], nil
+	// Registry name is given by the path to the image.
+	return strings.Join(pathComponents[0:len(pathComponents)-1], containerFileSeparator), nil
 }


Test patch:
diff --git a/pkg/cmd/modeline_test.go b/pkg/cmd/modeline_test.go
index 9bc001b48..da2224a45 100644
--- a/pkg/cmd/modeline_test.go
+++ b/pkg/cmd/modeline_test.go
@@ -129,3 +129,40 @@ func TestModelineRunPropertyFiles(t *testing.T) {
 	assert.NotNil(t, cmd)
 	assert.Equal(t, []string{"run", fileName, "--property-file=" + propFileName}, flags)
 }
+
+func TestModelineInspectSimple(t *testing.T) {
+	dir, err := ioutil.TempDir("", "camel-k-test-")
+	assert.NoError(t, err)
+	defer os.RemoveAll(dir)
+
+	file := `
+		// camel-k: dependency=mvn:org.my:lib:1.0
+	`
+	fileName := path.Join(dir, "simple.groovy")
+	err = ioutil.WriteFile(fileName, []byte(file), 0777)
+	assert.NoError(t, err)
+
+	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "inspect", fileName})
+	assert.NoError(t, err)
+	assert.NotNil(t, cmd)
+	assert.Equal(t, []string{"inspect", fileName, "--dependency=mvn:org.my:lib:1.0"}, flags)
+}
+
+func TestModelineInspectMultipleDeps(t *testing.T) {
+	dir, err := ioutil.TempDir("", "camel-k-test-")
+	assert.NoError(t, err)
+	defer os.RemoveAll(dir)
+
+	file := `
+		// camel-k: dependency=mvn:org.my:lib:1.0
+		// camel-k: dependency=camel-k:camel-dep
+	`
+	fileName := path.Join(dir, "simple.groovy")
+	err = ioutil.WriteFile(fileName, []byte(file), 0777)
+	assert.NoError(t, err)
+
+	cmd, flags, err := NewKamelWithModelineCommand(context.TODO(), []string{"kamel", "inspect", fileName})
+	assert.NoError(t, err)
+	assert.NotNil(t, cmd)
+	assert.Equal(t, []string{"inspect", fileName, "--dependency=mvn:org.my:lib:1.0", "--dependency=camel-k:camel-dep"}, flags)
+}
diff --git a/pkg/cmd/version_test.go b/pkg/cmd/version_test.go
new file mode 100644
index 000000000..9f816d4de
--- /dev/null
+++ b/pkg/cmd/version_test.go
@@ -0,0 +1,73 @@
+/*
+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 cmd
+
+import (
+	"testing"
+
+	"github.com/apache/camel-k/pkg/util/test"
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/assert"
+)
+
+const cmdVersion = "version"
+
+func initializeVersionCmdOptions(t *testing.T) (*versionCmdOptions, *cobra.Command, RootCmdOptions) {
+	options, rootCmd := kamelTestPreAddCommandInit()
+	versionCmdOptions := addTestVersionCmd(*options, rootCmd)
+	kamelTestPostAddCommandInit(t, rootCmd)
+
+	return versionCmdOptions, rootCmd, *options
+}
+
+func addTestVersionCmd(options RootCmdOptions, rootCmd *cobra.Command) *versionCmdOptions {
+	//add a testing version of version Command
+	versionCmd, versionOptions := newCmdVersion(&options)
+	versionCmd.RunE = func(c *cobra.Command, args []string) error {
+		return nil
+	}
+	versionCmd.PostRunE = func(c *cobra.Command, args []string) error {
+		return nil
+	}
+	versionCmd.Args = test.ArbitraryArgs
+	rootCmd.AddCommand(versionCmd)
+	return versionOptions
+}
+
+func TestVersionNonExistingFlag(t *testing.T) {
+	_, rootCmd, _ := initializeVersionCmdOptions(t)
+	_, err := test.ExecuteCommand(rootCmd, cmdVersion, "--nonExistingFlag")
+	assert.NotNil(t, err)
+}
+
+func TestVersionOperatorFlag(t *testing.T) {
+	versionCmdOptions, rootCmd, _ := initializeVersionCmdOptions(t)
+	_, err := test.ExecuteCommand(rootCmd, cmdVersion, "--operator")
+	assert.Nil(t, err)
+	assert.Equal(t, true, versionCmdOptions.Operator)
+}
+
+func TestCompatibleVersions(t *testing.T) {
+	assert.Equal(t, true, compatibleVersions("1.3.0", "1.3.0"))
+	assert.Equal(t, true, compatibleVersions("1.3.0", "1.3.1"))
+	assert.Equal(t, true, compatibleVersions("1.3.0", "1.3.0-SNAPSHOT"))
+	assert.Equal(t, false, compatibleVersions("1.3.0", "1.2.0"))
+	assert.Equal(t, false, compatibleVersions("1.3.0", "2.3.0"))
+	assert.Equal(t, false, compatibleVersions("1.3.0", "dsadsa"))
+	assert.Equal(t, false, compatibleVersions("dsadsa", "1.3.4"))
+}