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

chore: Improve Camel dependency validation #4411

Merged
merged 1 commit into from
May 25, 2023
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/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
63 changes: 33 additions & 30 deletions pkg/util/camel/camel_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,53 +49,56 @@ 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
// ValidateDependency validates a dependency 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) {
for _, d := range dependencies {
ValidateDependency(catalog, d, out)
func ValidateDependency(catalog *RuntimeCatalog, dependency string, out io.Writer) {
if err := ValidateDependencyE(catalog, dependency); err != nil {
fmt.Fprintf(out, "Warning: %s\n", err.Error())
}
}

// ValidateDependency validates a dependency 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 ValidateDependency(catalog *RuntimeCatalog, dependency string, out Output) {
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/v2/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)
})
}
}