Skip to content
New issue

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

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

Already on GitHub? # to your account

[Backport-1.12.x] chore: Improve Camel dependency validation #4414

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
7 changes: 7 additions & 0 deletions docs/modules/ROOT/pages/configuration/dependencies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ kamel run -d camel:http Integration.java
```
In this case, the dependency will be added with the correct version. Note that the standard notation for specifying a Camel dependency is `camel:xxx`, while `kamel` also accepts `camel-xxx` for usability.

While resolving Camel dependencies (`camel:xxx` or `camel-xxx`) the Camel K operator tries to find the dependency in the xref:architecture/cr/camel-catalog.adoc[Camel catalog].
In case the dependency is not listed in the catalog for some reason you will be provided with am error.
Please make sure to use Camel dependencies listed in the catalog as these components are eligible to being used in Camel K (e.g. due to proper version resolving and runtime optimization).
Using Camel dependencies not listed in the catalog may lead to unexpected behavior and is not supported.
In case you do have a custom Camel component that you want to use as part of an Integration you can add this as an external Maven dependency using the respective Maven coordinates of your project.
Please do not use one of the reserved the Camel groupIds (`org.apache.camel`) in that case.

*External dependencies* can be added using the `-d` flag, the `mvn` prefix, and the maven coordinates:
```
kamel run -d mvn:com.google.guava:guava:26.0-jre Integration.java
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func GetDependencies(ctx context.Context, cmd *cobra.Command, srcs, userDependen
}

// Validate user-provided dependencies against Camel catalog
camel.ValidateDependencies(catalog, userDependencies, cmd)
camel.ValidateDependencies(catalog, userDependencies, cmd.ErrOrStderr())

// Get top-level dependencies from sources
dependencies, err := getTopLevelDependencies(ctx, catalog, srcs)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/run_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (

func addDependency(cmd *cobra.Command, it *v1.Integration, dependency string, catalog *camel.RuntimeCatalog) {
normalized := camel.NormalizeDependency(dependency)
camel.ValidateDependency(catalog, normalized, cmd)
camel.ValidateDependency(catalog, normalized, cmd.ErrOrStderr())
it.Spec.AddDependency(normalized)
}

Expand Down
58 changes: 35 additions & 23 deletions pkg/util/camel/camel_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,53 +49,65 @@ func NormalizeDependency(dependency string) string {
return newDep
}

type Output interface {
OutOrStdout() io.Writer
ErrOrStderr() io.Writer
}

// ValidateDependencies validates dependencies against Camel catalog.
// It only shows warning and does not throw error in case the Catalog is just not complete
// and we don't want to let it stop the process.
func ValidateDependencies(catalog *RuntimeCatalog, dependencies []string, out Output) {
func ValidateDependencies(catalog *RuntimeCatalog, dependencies []string, out io.Writer) {
for _, d := range dependencies {
ValidateDependency(catalog, d, out)
}
}

// ValidateDependency validates a dependency against Camel catalog.
// It only shows warning and does not throw error in case the Catalog is just not complete
// It only shows warning and does not throw error in case the Catalog is just not complete,
// and we don't want to let it stop the process.
func ValidateDependency(catalog *RuntimeCatalog, dependency string, out Output) {
func ValidateDependency(catalog *RuntimeCatalog, dependency string, out io.Writer) {
if err := ValidateDependencyE(catalog, dependency); err != nil {
fmt.Fprintf(out, "Warning: %s\n", err.Error())
}

switch {
case strings.HasPrefix(dependency, "camel:"):
artifact := strings.TrimPrefix(dependency, "camel:")
if ok := catalog.IsValidArtifact(artifact); !ok {
fmt.Fprintf(out.ErrOrStderr(), "Warning: dependency %s not found in Camel catalog\n", dependency)
}
case strings.HasPrefix(dependency, "mvn:org.apache.camel:"):
component := strings.Split(dependency, ":")[2]
fmt.Fprintf(out.ErrOrStderr(), "Warning: do not use %s. Use %s instead\n",
fmt.Fprintf(out, "Warning: do not use %s. Use %s instead\n",
dependency, NormalizeDependency(component))
case strings.HasPrefix(dependency, "mvn:org.apache.camel.quarkus:"):
component := strings.Split(dependency, ":")[2]
fmt.Fprintf(out.ErrOrStderr(), "Warning: do not use %s. Use %s instead\n",
fmt.Fprintf(out, "Warning: do not use %s. Use %s instead\n",
dependency, NormalizeDependency(component))
}
}

// ValidateDependencyE validates a dependency against Camel catalog and throws error
// in case it does not exist in the catalog.
func ValidateDependencyE(catalog *RuntimeCatalog, dependency string) error {
var artifact string
switch {
case strings.HasPrefix(dependency, "camel:"):
artifact = strings.TrimPrefix(dependency, "camel:")
case strings.HasPrefix(dependency, "camel-quarkus:"):
artifact = strings.TrimPrefix(dependency, "camel-quarkus:")
case strings.HasPrefix(dependency, "camel-"):
artifact = dependency
}

if artifact == "" {
return nil
}

if ok := catalog.IsValidArtifact(artifact); !ok {
return fmt.Errorf("dependency %s not found in Camel catalog", dependency)
}

return nil
}

// ValidateDependenciesE validates dependencies against Camel catalog and throws error
// if it doesn't exist in the catalog.
// in case it does not exist in the catalog.
func ValidateDependenciesE(catalog *RuntimeCatalog, dependencies []string) error {
for _, dependency := range dependencies {
if !strings.HasPrefix(dependency, "camel:") {
continue
}

artifact := strings.TrimPrefix(dependency, "camel:")
if ok := catalog.IsValidArtifact(artifact); !ok {
return fmt.Errorf("dependency %s not found in Camel catalog", dependency)
if err := ValidateDependencyE(catalog, dependency); err != nil {
return err
}
}

Expand Down
128 changes: 128 additions & 0 deletions pkg/util/camel/camel_dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ limitations under the License.
package camel

import (
"fmt"
"strings"
"testing"

"github.com/apache/camel-k/pkg/util/maven"
"github.com/stretchr/testify/assert"
)

Expand All @@ -30,4 +33,129 @@ func TestNormalizeDependency(t *testing.T) {
assert.Equal(t, "camel:file", NormalizeDependency("camel-quarkus:file"))
assert.Equal(t, "camel-k:knative", NormalizeDependency("camel-k-knative"))
assert.Equal(t, "camel-k:knative", NormalizeDependency("camel-k:knative"))
assert.Equal(t, "mvn:org.apache.camel:camel-file", NormalizeDependency("mvn:org.apache.camel:camel-file"))
assert.Equal(t, "mvn:org.apache.camel.quarkus:camel-quarkus-file", NormalizeDependency("mvn:org.apache.camel.quarkus:camel-quarkus-file"))
assert.Equal(t, "mvn:org.apache.camel:camel-k-knative", NormalizeDependency("mvn:org.apache.camel:camel-k-knative"))
}

func TestValidateDependency(t *testing.T) {
catalog, err := DefaultCatalog()
assert.Nil(t, err)

output := strings.Builder{}
ValidateDependency(catalog, "", &output)
assert.Equal(t, "", output.String())

output.Reset()
ValidateDependency(catalog, "camel:file", &output)
assert.Equal(t, "", output.String())

output.Reset()
ValidateDependency(catalog, "camel-quarkus-file", &output)
assert.Equal(t, "", output.String())

output.Reset()
ValidateDependency(catalog, "camel-quarkus:file", &output)
assert.Equal(t, "", output.String())

output.Reset()
ValidateDependency(catalog, "camel:unknown", &output)
assert.Equal(t, "Warning: dependency camel:unknown not found in Camel catalog\n", output.String())

output.Reset()
ValidateDependency(catalog, "mvn:org.apache.camel:camel-foo", &output)
assert.Equal(t, "Warning: do not use mvn:org.apache.camel:camel-foo. Use camel:foo instead\n", output.String())

output.Reset()
ValidateDependency(catalog, "mvn:org.apache.camel.quarkus:camel-quarkus-foo", &output)
assert.Equal(t, "Warning: do not use mvn:org.apache.camel.quarkus:camel-quarkus-foo. Use camel:foo instead\n", output.String())
}

func TestManageIntegrationDependencies(t *testing.T) {
catalog, err := DefaultCatalog()
assert.Nil(t, err)

tests := []struct {
name string
dependencies []string
coordinates string
}{
{
name: "basic_camel",
dependencies: []string{
"camel:direct",
"camel:log",
"camel:core",
},
coordinates: "org.apache.camel.quarkus:camel-quarkus-direct," +
"org.apache.camel.quarkus:camel-quarkus-log," +
"org.apache.camel.quarkus:camel-quarkus-core",
},
{
name: "camel_quarkus",
dependencies: []string{
"camel:direct",
"camel-quarkus:log",
"camel:camel-quarkus-core",
},
coordinates: "org.apache.camel.quarkus:camel-quarkus-direct," +
"org.apache.camel.quarkus:camel-quarkus-log," +
"org.apache.camel.quarkus:camel-quarkus-core",
},
{
name: "camel_k",
dependencies: []string{
"camel:direct",
"camel-k:webhook",
},
coordinates: "org.apache.camel.quarkus:camel-quarkus-direct," +
"org.apache.camel.k:camel-k-webhook",
},
{
name: "not_in_catalog",
dependencies: []string{
"camel:direct",
"camel:resiliance4j",
},
coordinates: "org.apache.camel.quarkus:camel-quarkus-direct," +
"org.apache.camel.quarkus:camel-quarkus-resiliance4j",
},
{
name: "mvn",
dependencies: []string{
"mvn:org.foo:bar",
"mvn:org.apache.camel:camel-resiliance4j",
},
coordinates: "org.foo:bar," +
"org.apache.camel:camel-resiliance4j",
},
{
name: "jitpack",
dependencies: []string{
"github:apache/camel-sample/1.0",
},
coordinates: "com.github.apache:camel-sample",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
project := maven.Project{}

err = ManageIntegrationDependencies(&project, test.dependencies, catalog)
assert.Nil(t, err)

coordinates := strings.Builder{}
for i, d := range project.Dependencies {
if i == 0 {
_, err = fmt.Fprintf(&coordinates, "%s:%s", d.GroupID, d.ArtifactID)
assert.Nil(t, err)
} else {
_, err = fmt.Fprintf(&coordinates, ",%s:%s", d.GroupID, d.ArtifactID)
assert.Nil(t, err)
}
}
assert.Equal(t, test.coordinates, coordinates.String(), coordinates)
})
}
}