This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kamelets.git
The following commit(s) were added to refs/heads/main by this push:
new 5facd1014 Fix #1888: replace the Go catalog validator with Java checks
in the Maven plugin (#3048)
5facd1014 is described below
commit 5facd10143871d51174bcbc1a035ca10d7127940
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Sep 18 10:55:47 2026 +0200
Fix #1888: replace the Go catalog validator with Java checks in the Maven
plugin (#3048)
* Fix #1888: replace the Go catalog validator with Java checks in the Maven
plugin
Second half of @claudio4j's request. The eleven checks from
script/validator are
ported into CatalogValidator and run from ValidateKameletsMojo, so there is
one
validate goal rather than a Maven plugin plus a Go toolchain.
The checks, their two phase order and their messages are kept as they were,
so
the same catalog produces the same output.
Two deliberate differences.
The title capitalization rule used github.com/bbalet/stopwords, whose
English
list is far broader than the classic one: it treats name, between, from and
now
as stop words, which is why titles such as "Cache name" and "Period between
Polls" pass today. Rather than take a dependency on a Java stop word
library for
a word list, the classic list is embedded and those extra words are kept as
an
explicit exemption set, so nothing that passes today starts failing.
Tightening
the rule and fixing those titles is a separate change.
The verifyUsedParams exemption list keyed on hardcoded relative paths, so
it only
applied when the validator was run as "go run . ../../kamelets/" from
script/validator. Run against the same catalog by any other path it
reported 13
errors on unmodified files. The port keys on the Kamelet name instead,
which is
path independent.
Verified against the Go implementation on the same inputs:
clean catalog 0 errors both
five phase one violations identical output
three phase two violations identical output
The Go validator needed its expected directory layout for the last
comparison,
for the path reason above.
validate.yaml now builds the bom module instead of installing Go, and the
Makefile validate target does the same. README.md, AGENTS.md and
security-model.adoc are updated; script/ now holds only the version bump
helper.
Co-Authored-By: Claude Opus 5 <[email protected]>
* Fix #1888: run the validate job through install, not compile
The validate job failed with:
Failed to parse plugin descriptor for
kamelets-maven-plugin:4.23.0-SNAPSHOT
(library/kamelets-maven-plugin/target/classes):
No plugin descriptor found at META-INF/maven/plugin.xml
With -am the plugin is built from source in the same reactor rather than
resolved
from the repository, and maven-plugin-plugin only writes
META-INF/maven/plugin.xml
from process-classes onwards. Stopping at compile therefore left the bom
module
trying to invoke a plugin that had no descriptor yet.
Using install with -DskipTests takes the plugin through packaging first, so
the
descriptor exists by the time the bom runs the goal. The Makefile validate
target
had the same command and the same problem.
This did not show up locally because the plugin was already installed in the
local repository, so the descriptor was read from the installed jar.
Reproduced
after mvn clean, then confirmed fixed the same way.
Co-Authored-By: Claude Opus 5 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>
---
.github/workflows/validate.yaml | 14 +-
AGENTS.md | 10 +-
Makefile | 3 +-
README.md | 9 +-
docs/modules/ROOT/pages/security-model.adoc | 2 +-
.../kamelets/maven/plugin/CatalogValidator.java | 542 +++++++++++++++++++++
.../maven/plugin/ValidateKameletsMojo.java | 39 ++
script/validator/README.md | 8 -
script/validator/go.mod | 34 --
script/validator/go.sum | 100 ----
script/validator/validator.go | 506 -------------------
11 files changed, 598 insertions(+), 669 deletions(-)
diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml
index f5b2a47d6..85b08e19f 100644
--- a/.github/workflows/validate.yaml
+++ b/.github/workflows/validate.yaml
@@ -50,13 +50,17 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - name: Install Go
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
+ - name: Set up JDK
+ uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 #
v6.0.1
with:
- go-version: 1.25.x
+ distribution: 'temurin'
+ java-version: 17
+ cache: 'maven'
- name: Run Validator
run: |
echo "Running Kamelet validator..."
- cd script/validator
- go run . ../../kamelets/
+ # install, not compile: with -am the plugin is built from source in the
+ # same reactor, and its descriptor is only generated from
process-classes
+ # onwards, so an earlier phase leaves the bom unable to invoke it
+ ./mvnw -V -ntp -pl library/camel-kamelets-bom -am install -DskipTests
diff --git a/AGENTS.md b/AGENTS.md
index 8f6ed8af7..eef751e88 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,8 +11,7 @@ Camel, Camel K, Camel Quarkus and Camel Spring Boot.
- Version: 4.20.0
- Camel: 4.20.0
- Java: 17+
-- Build: Maven (`mvn verify`); Go is required for the `script/` validator and
- the `crds/` generator
+- Build: Maven (`mvn verify`); Go is required for the `crds/` generator
- Issue tracker: GitHub — https://github.com/apache/camel-kamelets/issues
- Related repository: `apache/camel` (Camel core — the Kamelet **runtime**
lives there, not here)
@@ -99,8 +98,7 @@ relevant committers:
- After adding or modifying a Kamelet, **regenerate and commit** the generated
docs and validate, or CI will fail:
```bash
- ./mvnw clean install # also regenerates nav.adoc
- cd script/validator && go run . ../../kamelets/ # must report no ERRORS
+ ./mvnw clean install # regenerates nav.adoc and validates the catalog
```
- `nav.adoc` and the per-Kamelet doc pages are **generated** — do not hand-edit
them.
@@ -290,7 +288,7 @@ camel-kamelets/
│ ├── camel-kamelets-crds/ # Fabric8-generated K8s CRD POJOs (Java)
│ └── kamelets-maven-plugin/ # build-time validation plugin
├── crds/ # Go CRD client generator (build/CI)
-├── script/ # Go YAML validator (build/CI)
+├── script/ # version-bump helper script
├── templates/ # init .vm template + Pipe examples
├── tests/camel-kamelets-itest/ # Citrus integration tests
└── docs/modules/ROOT/ # Antora AsciiDoc (security-model.adoc lives
here)
@@ -301,7 +299,7 @@ camel-kamelets/
```bash
mvn verify # full build (from root)
mvn verify -Pcoverage # with coverage
-cd script/validator && go run . ../../kamelets/ # validate (nav.adoc is
regenerated by the build)
+# nav.adoc regeneration and catalog validation both run as part of the build
```
## Kamelet Authoring Conventions
diff --git a/Makefile b/Makefile
index e3d55f319..a6418e522 100644
--- a/Makefile
+++ b/Makefile
@@ -28,5 +28,4 @@ generate:
./gen_crd/gen_crd_api.sh
validate:
- cd script/validator && \
- go run . ../../kamelets/
+ ./mvnw -pl library/camel-kamelets-bom -am install -DskipTests
diff --git a/README.md b/README.md
index 829ad4ec9..1593f0e29 100644
--- a/README.md
+++ b/README.md
@@ -25,13 +25,8 @@ $ ./mvnw clean install
> [!IMPORTANT]
> After adding or modifying a kamelet, `./mvnw clean install` regenerates the
-> Antora navigation for you, so there is nothing extra to run for the docs.
-> Still validate:
-> ```bash
-> $ cd script/validator
-> $ go run . ../../kamelets/
-> ```
-> be sure that there aren't reported ERRORS.
+> Antora navigation and validates the catalog, so there is nothing extra to
run.
+> Be sure that there aren't reported ERRORS.
### General Format
diff --git a/docs/modules/ROOT/pages/security-model.adoc
b/docs/modules/ROOT/pages/security-model.adoc
index 2f7fdb794..17e5e9ddb 100644
--- a/docs/modules/ROOT/pages/security-model.adoc
+++ b/docs/modules/ROOT/pages/security-model.adoc
@@ -163,7 +163,7 @@ first is the security-relevant product.
| Maven BOM (pom only) and the resource bundle that jars family 1.
| Only as the delivery vehicle for family 1
-| `script/` (Go validator), `crds/` Go client generator,
+| `script/` helper scripts, `crds/` Go client generator,
`tests/camel-kamelets-itest` (Citrus), `templates/` (init `.vm` and Pipe
examples)
| CI, documentation generation, test and scaffolding code. Not shipped to a
diff --git
a/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/CatalogValidator.java
b/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/CatalogValidator.java
new file mode 100644
index 000000000..785c87c9c
--- /dev/null
+++
b/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/CatalogValidator.java
@@ -0,0 +1,542 @@
+/*
+ * 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 org.apache.camel.kamelets.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+/**
+ * Catalog-wide conventions for the Kamelet YAML files.
+ *
+ * Ported from the Go validator that used to live in script/validator. The
checks,
+ * their order and their messages are kept as they were, so the same catalog
+ * produces the same output.
+ */
+public final class CatalogValidator {
+
+ static final String TYPE_LABEL = "camel.apache.org/kamelet.type";
+ static final String ICON_ANNOTATION = "camel.apache.org/kamelet.icon";
+ static final String VERSION_ANNOTATION =
"camel.apache.org/catalog.version";
+ static final String PROVIDER_ANNOTATION = "camel.apache.org/provider";
+ static final String EXPECTED_PROVIDER = "Apache Software Foundation";
+ static final String CREDENTIALS_DESCRIPTOR = "urn:camel:group:credentials";
+ static final String DEPRECATED_DESCRIPTOR_PREFIX =
"urn:alm:descriptor:com.tectonic.ui";
+
+ private static final Pattern PARAM =
Pattern.compile("\\{\\{[?]?([A-Za-z0-9\\-._]+)(?:[:][^}]*)?}}");
+
+ /**
+ * The classic English stop word list. Words here are exempt from the
+ * property-title capitalization rule.
+ */
+ private static final Set<String> STOP_WORDS = Set.of(
+ "a", "an", "and", "are", "as", "at", "be", "but", "by", "for",
"if", "in",
+ "into", "is", "it", "no", "not", "of", "on", "or", "such", "that",
"the",
+ "their", "then", "there", "these", "they", "this", "to", "was",
"will", "with");
+
+ /**
+ * Words the Go implementation also treated as stop words, through a list
that was
+ * far broader than the classic one. They are kept so that this port does
not start
+ * failing titles the catalog already contains, such as "Cache name" or
+ * "Period between Polls". Tightening the rule and fixing those titles is
a separate
+ * change.
+ */
+ private static final Set<String> EXTRA_EXEMPT = Set.of(
+ "name", "between", "from", "now", "about", "after", "before",
"down", "during",
+ "only", "over", "same", "some", "than", "too", "under", "up",
"very", "when",
+ "where", "which", "while", "who", "why", "you", "your");
+
+ /** Kamelets whose declared and used parameters are deliberately allowed
to differ. */
+ private static final Set<String> USED_PARAMS_EXEMPT = Set.of(
+ "azure-storage-blob-source",
+ "aws-s3-event-based-source",
+ "ceph-event-based-source",
+ "aws-sqs-source",
+ "set-kafka-key-action",
+ "azure-storage-blob-event-based-source",
+ "google-storage-event-based-source",
+ "elasticsearch-search-source",
+ "opensearch-search-source",
+ "kafka-azure-schema-registry-source",
+ "kafka-azure-schema-registry-sink",
+ "kafka-batch-azure-schema-registry-source",
+ "cassandra-sink",
+ "data-type-action");
+
+ private CatalogValidator() {
+ }
+
+ /** One parsed Kamelet plus the file it came from. */
+ static final class KameletInfo {
+ final String name;
+ final File file;
+ final Map<String, Object> doc;
+
+ KameletInfo(String name, File file, Map<String, Object> doc) {
+ this.name = name;
+ this.file = file;
+ this.doc = doc;
+ }
+
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map(Map<String, Object> parent, String key) {
+ if (parent == null) {
+ return null;
+ }
+ Object v = parent.get(key);
+ return v instanceof Map ? (Map<String, Object>) v : null;
+ }
+
+ Map<String, Object> metadata() {
+ return map(doc, "metadata");
+ }
+
+ Map<String, Object> spec() {
+ return map(doc, "spec");
+ }
+
+ Map<String, Object> definition() {
+ return map(spec(), "definition");
+ }
+
+ Map<String, Object> template() {
+ return map(spec(), "template");
+ }
+
+ Map<String, Object> properties() {
+ Map<String, Object> def = definition();
+ Map<String, Object> p = map(def, "properties");
+ return p != null ? p : new LinkedHashMap<>();
+ }
+
+ String label(String key) {
+ Map<String, Object> labels = map(metadata(), "labels");
+ Object v = labels != null ? labels.get(key) : null;
+ return v != null ? v.toString() : "";
+ }
+
+ String annotation(String key) {
+ Map<String, Object> ann = map(metadata(), "annotations");
+ Object v = ann != null ? ann.get(key) : null;
+ return v != null ? v.toString() : "";
+ }
+
+ @SuppressWarnings("unchecked")
+ List<String> required() {
+ Object v = definition() != null ? definition().get("required") :
null;
+ List<String> out = new ArrayList<>();
+ if (v instanceof List) {
+ for (Object o : (List<Object>) v) {
+ out.add(String.valueOf(o));
+ }
+ }
+ return out;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<String> dependencies() {
+ Object v = spec() != null ? spec().get("dependencies") : null;
+ List<String> out = new ArrayList<>();
+ if (v instanceof List) {
+ for (Object o : (List<Object>) v) {
+ out.add(String.valueOf(o));
+ }
+ }
+ return out;
+ }
+ }
+
+ /** Reads every *.kamelet.yaml in the directory, ordered by file path. */
+ static List<KameletInfo> listKamelets(File dir) throws IOException {
+ File[] files = dir.listFiles(f -> f.isFile() &&
f.getName().endsWith(".kamelet.yaml"));
+ if (files == null) {
+ throw new IOException("cannot list dir \"" + dir + "\"");
+ }
+ List<File> sorted = new ArrayList<>(Arrays.asList(files));
+ sorted.sort(Comparator.comparing(File::getPath));
+
+ ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
+ List<KameletInfo> out = new ArrayList<>(sorted.size());
+ for (File f : sorted) {
+ Map<String, Object> doc;
+ try {
+ doc = mapper.readValue(f, Map.class);
+ } catch (IOException e) {
+ throw new IOException("kamelet \"" + f.getName() + "\" is not
a valid YAML file", e);
+ }
+ Object md = doc.get("metadata");
+ Object name = md instanceof Map ? ((Map<?, ?>) md).get("name") :
null;
+ out.add(new KameletInfo(name != null ? name.toString() : "", f,
doc));
+ }
+ return out;
+ }
+
+ /**
+ * Runs every check in the order the Go validator used: the structural
ones first,
+ * because the rest assume they passed.
+ */
+ static List<String> validate(List<KameletInfo> kamelets) {
+ List<String> errors = new ArrayList<>();
+ verifyFileNames(kamelets, errors);
+ verifyKameletType(kamelets, errors);
+ verifyParameters(kamelets, errors);
+ if (!errors.isEmpty()) {
+ return errors;
+ }
+ verifyAnnotations(kamelets, errors);
+ verifyDescriptors(kamelets, errors);
+ verifyDuplicates(kamelets, errors);
+ verifyUsedParams(kamelets, errors);
+ verifyTemplateConvention(kamelets, errors);
+ verifyDependencies(kamelets, errors);
+ verifyCatalogVersion(kamelets, errors);
+ return errors;
+ }
+
+ private static String q(Object o) {
+ return "\"" + o + "\"";
+ }
+
+ static void verifyFileNames(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ String expected =
k.file.getName().replaceAll("\\.kamelet\\.yaml$", "");
+ if (!k.name.equals(expected)) {
+ errors.add("file " + q(k.file.getPath()) + " does not match
the name of the contained kamelet: " + q(k.name));
+ }
+ }
+ }
+
+ static void verifyKameletType(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ String tp = k.label(TYPE_LABEL);
+ if ("source".equals(tp) || "sink".equals(tp) ||
"action".equals(tp)) {
+ String suffix = "-" + tp;
+ if (!k.name.endsWith(suffix)) {
+ errors.add("name of kamelet " + q(k.name) + " does not end
with " + q(suffix));
+ }
+ } else {
+ errors.add("kamelet " + q(k.name) + " contains an invalid
value for the " + TYPE_LABEL + " label: " + q(tp));
+ }
+ }
+ }
+
+ static void verifyParameters(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ if (k.definition() == null) {
+ errors.add("kamelet " + q(k.name) + " does not contain the
JSON schema definition");
+ continue;
+ }
+ if (k.template() == null) {
+ errors.add("kamelet " + q(k.name) + " does not contain the
Template specification");
+ continue;
+ }
+ Set<String> seenRequired = new HashSet<>();
+ for (String p : k.required()) {
+ if (!seenRequired.add(p)) {
+ errors.add("required kamelet property " + q(p) + " is
listed twice in kamelet " + q(k.name));
+ }
+ }
+ Object titleObj = k.definition().get("title");
+ String title = titleObj != null ? titleObj.toString() : "";
+ if (title.isEmpty()) {
+ errors.add("kamelet " + q(k.name) + " does not contain title");
+ } else {
+ String tp = k.label(TYPE_LABEL);
+ if (tp.length() > 1) {
+ String expectedSuffix = tp.substring(0, 1).toUpperCase() +
tp.substring(1);
+ if (!title.endsWith(expectedSuffix)) {
+ errors.add("kamelet " + q(k.name) + " title " +
q(title) + " does not ends with " + q(expectedSuffix));
+ }
+ }
+ }
+ Object descObj = k.definition().get("description");
+ if (descObj == null || descObj.toString().isEmpty()) {
+ errors.add("kamelet " + q(k.name) + " does not contain
description");
+ }
+ if (!"object".equals(String.valueOf(k.definition().get("type")))) {
+ errors.add("kamelet " + q(k.name) + " does not contain a
definition of type \"object\"");
+ }
+ for (Map.Entry<String, Object> e : k.properties().entrySet()) {
+ verifyProperty(k, e.getKey(), asMap(e.getValue()), errors);
+ }
+ Map<String, Object> props = k.properties();
+ for (String r : k.required()) {
+ if (!props.containsKey(r)) {
+ errors.add("required property " + q(r) + " in kamelet " +
q(k.name) + " is not defined");
+ }
+ }
+ }
+ }
+
+ private static void verifyProperty(KameletInfo k, String key, Map<String,
Object> p, List<String> errors) {
+ if (p == null) {
+ return;
+ }
+ String type = str(p.get("type"));
+ String title = str(p.get("title"));
+ String description = str(p.get("description"));
+
+ if (type.isEmpty()) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
does not contain type");
+ }
+ if (title.isEmpty()) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
does not contain title");
+ } else {
+ for (String w : title.split(" ")) {
+ if (w.isEmpty() || isExempt(w)) {
+ continue;
+ }
+ String first = w.substring(0, 1);
+ if (!first.toUpperCase().equals(first)) {
+ errors.add("property " + q(key) + " in kamelet " +
q(k.name)
+ + " does has non-capitalized word in the title: "
+ q(w));
+ }
+ }
+ }
+ if (title.startsWith("The ")) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
has a title starting with \"The \"");
+ }
+ if (title.startsWith("A ")) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
has a title starting with \"A \"");
+ }
+ if (title.startsWith("An ")) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
has a title starting with \"An \"");
+ }
+ if (description.isEmpty()) {
+ errors.add("property " + q(key) + " in kamelet " + q(k.name) + "
does not contain a description");
+ }
+ }
+
+ /**
+ * A title word is exempt from capitalization when it is a stop word, or
when it
+ * carries trailing punctuation: the Go implementation compared the raw
word against
+ * a punctuation-stripped list, so "checks." never matched and was skipped.
+ */
+ private static boolean isExempt(String word) {
+ String lower = word.toLowerCase();
+ if (STOP_WORDS.contains(lower) || EXTRA_EXEMPT.contains(lower)) {
+ return true;
+ }
+ return !lower.equals(stripPunctuation(lower));
+ }
+
+ private static String stripPunctuation(String w) {
+ return w.replaceAll("[^\\p{Alnum}]", "");
+ }
+
+ static void verifyAnnotations(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ if (k.annotation(ICON_ANNOTATION).isEmpty()) {
+ errors.add("kamelet " + q(k.name) + " does not contain the " +
ICON_ANNOTATION + " annotation");
+ }
+ if (k.annotation(VERSION_ANNOTATION).isEmpty()) {
+ errors.add("kamelet " + q(k.name) + " does not contain the " +
VERSION_ANNOTATION
+ + " annotation (should match project version in
pom.xml)");
+ }
+ String provider = k.annotation(PROVIDER_ANNOTATION);
+ if (!EXPECTED_PROVIDER.equals(provider)) {
+ errors.add("kamelet " + q(k.name) + " does not contain the
right value for the " + PROVIDER_ANNOTATION
+ + " annotation: expected " + q(EXPECTED_PROVIDER) + ",
found " + q(provider));
+ }
+ }
+ }
+
+ static void verifyDescriptors(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ if (k.definition() == null) {
+ errors.add("kamelet " + q(k.name) + " does not contain the
JSON schema definition");
+ continue;
+ }
+ for (Map.Entry<String, Object> e : k.properties().entrySet()) {
+ Map<String, Object> p = asMap(e.getValue());
+ if (p == null) {
+ continue;
+ }
+ List<String> descriptors = strList(p.get("x-descriptors"));
+ if ("password".equals(str(p.get("format"))) &&
!descriptors.contains(CREDENTIALS_DESCRIPTOR)) {
+ errors.add("property " + q(e.getKey()) + " in kamelet " +
q(k.name)
+ + " has \"password\" format but misses descriptor
" + q(CREDENTIALS_DESCRIPTOR));
+ }
+ boolean deprecated = descriptors.stream().anyMatch(d ->
d.startsWith(DEPRECATED_DESCRIPTOR_PREFIX));
+ if (deprecated) {
+ errors.add("property " + q(e.getKey()) + " in kamelet " +
q(k.name)
+ + " uses the deprecated x-descriptor prefix " +
q(DEPRECATED_DESCRIPTOR_PREFIX)
+ + "; use " + q(CREDENTIALS_DESCRIPTOR) + "
instead");
+ }
+ Set<String> seen = new HashSet<>();
+ for (String d : descriptors) {
+ if (!seen.add(d)) {
+ errors.add("property " + q(e.getKey()) + " in kamelet
" + q(k.name)
+ + " lists the x-descriptor " + q(d) + " more
than once");
+ }
+ }
+ }
+ }
+ }
+
+ static void verifyDuplicates(List<KameletInfo> kamelets, List<String>
errors) {
+ Set<String> titles = new HashSet<>();
+ Set<String> descriptions = new HashSet<>();
+ for (KameletInfo k : kamelets) {
+ if (k.definition() == null) {
+ errors.add("kamelet " + q(k.name) + " does not contain the
JSON schema definition");
+ continue;
+ }
+ String title = str(k.definition().get("title"));
+ if (!titles.add(title)) {
+ errors.add("kamelet " + q(k.name) + " has duplicate title " +
q(title));
+ }
+ String description = str(k.definition().get("description"));
+ if (!descriptions.add(description)) {
+ errors.add("kamelet " + q(k.name) + " has duplicate
description " + q(description));
+ }
+ }
+ }
+
+ static void verifyTemplateConvention(List<KameletInfo> kamelets,
List<String> errors) {
+ for (KameletInfo k : kamelets) {
+ if (k.template() == null) {
+ continue;
+ }
+ String template = k.template().toString();
+ String tp = k.label(TYPE_LABEL);
+ if ("source".equals(tp) && !template.contains("kamelet:sink")) {
+ errors.add("source kamelet " + q(k.name) + " must route to
\"kamelet:sink\"");
+ } else if ("sink".equals(tp) &&
!template.contains("kamelet:source")) {
+ errors.add("sink kamelet " + q(k.name) + " must consume from
\"kamelet:source\"");
+ }
+ }
+ }
+
+ static void verifyDependencies(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ for (String dep : k.dependencies()) {
+ if (!dep.startsWith("camel:") && !dep.startsWith("mvn:") &&
!dep.startsWith("github:apache/")) {
+ errors.add("kamelet " + q(k.name) + " declares dependency
" + q(dep)
+ + " that is not a \"camel:\",
\"mvn:group:artifact:version\" or \"github:apache/...\" reference");
+ }
+ }
+ }
+ }
+
+ static void verifyCatalogVersion(List<KameletInfo> kamelets, List<String>
errors) {
+ Map<String, Integer> versions = new HashMap<>();
+ for (KameletInfo k : kamelets) {
+ versions.merge(k.annotation(VERSION_ANNOTATION), 1, Integer::sum);
+ }
+ if (versions.size() > 1) {
+ Set<String> summary = new TreeSet<>();
+ for (Map.Entry<String, Integer> e : versions.entrySet()) {
+ summary.add(q(e.getKey()) + " (" + e.getValue() + "
kamelets)");
+ }
+ errors.add("inconsistent " + VERSION_ANNOTATION + " across the
catalog: " + String.join(", ", summary));
+ }
+ }
+
+ static void verifyUsedParams(List<KameletInfo> kamelets, List<String>
errors) {
+ for (KameletInfo k : kamelets) {
+ if (USED_PARAMS_EXEMPT.contains(k.name)) {
+ continue;
+ }
+ Set<String> used = new TreeSet<>();
+ collectParams(k.template(), used);
+
+ Set<String> declared = new TreeSet<>(k.properties().keySet());
+ declared.addAll(beanNames(k));
+
+ for (String p : used) {
+ if (!declared.contains(p)) {
+ errors.add("parameter " + q(p) + " is not declared in the
definition of kamelet " + q(k.name));
+ }
+ }
+ for (String p : declared) {
+ if (!used.contains(p)) {
+ errors.add("parameter " + q(p) + " is declared in kamelet
" + q(k.name) + " but never used");
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Set<String> beanNames(KameletInfo k) {
+ Set<String> out = new HashSet<>();
+ Map<String, Object> template = k.template();
+ Object beans = template != null ? template.get("beans") : null;
+ if (beans instanceof List) {
+ for (Object b : (List<Object>) beans) {
+ Map<String, Object> bm = asMap(b);
+ if (bm != null && bm.get("name") != null) {
+ out.add(bm.get("name").toString());
+ }
+ }
+ }
+ return out;
+ }
+
+ /** Walks the template and collects every {{param}} placeholder it
mentions. */
+ @SuppressWarnings("unchecked")
+ private static void collectParams(Object node, Set<String> params) {
+ if (node instanceof String) {
+ Matcher m = PARAM.matcher((String) node);
+ while (m.find()) {
+ params.add(m.group(1));
+ }
+ } else if (node instanceof List) {
+ for (Object c : (List<Object>) node) {
+ collectParams(c, params);
+ }
+ } else if (node instanceof Map) {
+ for (Object c : ((Map<String, Object>) node).values()) {
+ collectParams(c, params);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map<String, Object> asMap(Object o) {
+ return o instanceof Map ? (Map<String, Object>) o : null;
+ }
+
+ private static String str(Object o) {
+ return o != null ? o.toString() : "";
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<String> strList(Object o) {
+ List<String> out = new ArrayList<>();
+ if (o instanceof List) {
+ for (Object c : (List<Object>) o) {
+ out.add(String.valueOf(c));
+ }
+ }
+ return out;
+ }
+}
diff --git
a/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/ValidateKameletsMojo.java
b/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/ValidateKameletsMojo.java
index 4ae922104..e215a7f49 100644
---
a/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/ValidateKameletsMojo.java
+++
b/library/kamelets-maven-plugin/src/main/java/org/apache/camel/kamelets/maven/plugin/ValidateKameletsMojo.java
@@ -61,8 +61,17 @@ public class ValidateKameletsMojo extends AbstractMojo {
@Parameter(property = "kamelets.failOnError", defaultValue = "true")
private boolean failOnError = true;
+ /**
+ * Directory holding the *.kamelet.yaml files, validated against the
catalog
+ * conventions ported from the Go validator.
+ */
+ @Parameter(property = "kamelets.dir", defaultValue =
"${project.basedir}/../../kamelets")
+ private File kameletsDir;
+
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
+ validateCatalogConventions();
+
String[] bannedDeps = {"mvn:", "camel:gson", "camel:core",
"camel:kamelet"};
List<String> bannedDepsList = Arrays.asList(bannedDeps);
KameletsCatalog catalog = new KameletsCatalog();
@@ -134,4 +143,34 @@ public class ValidateKameletsMojo extends AbstractMojo {
}
getLog().info("Validation passed");
}
+
+ /**
+ * Catalog-wide conventions over the Kamelet YAML files: naming,
annotations,
+ * descriptors, declared versus used parameters and so on. Ported from the
Go
+ * validator that used to live in script/validator.
+ */
+ private void validateCatalogConventions() throws MojoExecutionException {
+ if (!kameletsDir.isDirectory()) {
+ throw new MojoExecutionException("Cannot list dir " + kameletsDir);
+ }
+ List<CatalogValidator.KameletInfo> kamelets;
+ try {
+ kamelets = CatalogValidator.listKamelets(kameletsDir);
+ } catch (java.io.IOException e) {
+ throw new MojoExecutionException("Cannot read the Kamelets in " +
kameletsDir, e);
+ }
+ if (kamelets.isEmpty()) {
+ throw new MojoExecutionException("Directory " + kameletsDir + "
has no Kamelets");
+ }
+
+ List<String> errors = CatalogValidator.validate(kamelets);
+ for (String error : errors) {
+ getLog().error("ERROR: " + error);
+ }
+ if (!errors.isEmpty() && failOnError) {
+ throw new MojoExecutionException(
+ "The Kamelets Validation failed with " + errors.size() + "
error(s). See logs for more information.\n");
+ }
+ getLog().info("Catalog conventions validated over " + kamelets.size()
+ " kamelets");
}
+}
diff --git a/script/validator/README.md b/script/validator/README.md
deleted file mode 100644
index 9b2d44e70..000000000
--- a/script/validator/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Kamelet Validator
-
-To validate the Kamelets:
-
-```
-# From the script/validator directory
-go run . ../../kamelets/
-```
diff --git a/script/validator/go.mod b/script/validator/go.mod
deleted file mode 100644
index 72f7034e6..000000000
--- a/script/validator/go.mod
+++ /dev/null
@@ -1,34 +0,0 @@
-module github.com/apache/camel-kamelets/script/validator
-
-go 1.25.0
-
-require (
- github.com/apache/camel-kamelets/crds v0.0.0
- github.com/bbalet/stopwords v1.0.0
- github.com/pkg/errors v0.9.1
- k8s.io/apimachinery v0.33.4
-)
-
-replace github.com/apache/camel-kamelets/crds => ../../crds
-
-require (
- github.com/fxamacker/cbor/v2 v2.8.0 // indirect
- github.com/go-logr/logr v1.4.3 // indirect
- github.com/gogo/protobuf v1.3.2 // indirect
- github.com/json-iterator/go v1.1.12 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd //
indirect
- github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/spf13/pflag v1.0.6 // indirect
- github.com/x448/float16 v0.8.4 // indirect
- golang.org/x/net v0.55.0 // indirect
- golang.org/x/text v0.37.0 // indirect
- gopkg.in/inf.v0 v0.9.1 // indirect
- k8s.io/api v0.33.4 // indirect
- k8s.io/klog/v2 v2.130.1 // indirect
- k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
- sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
- sigs.k8s.io/randfill v1.0.0 // indirect
- sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
- sigs.k8s.io/yaml v1.4.0 // indirect
-)
diff --git a/script/validator/go.sum b/script/validator/go.sum
deleted file mode 100644
index 89060cf19..000000000
--- a/script/validator/go.sum
+++ /dev/null
@@ -1,100 +0,0 @@
-github.com/bbalet/stopwords v1.0.0
h1:0TnGycCtY0zZi4ltKoOGRFIlZHv0WqpoIGUsObjztfo=
-github.com/bbalet/stopwords v1.0.0/go.mod
h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc=
-github.com/creack/pty v1.1.9/go.mod
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fxamacker/cbor/v2 v2.8.0
h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
-github.com/fxamacker/cbor/v2 v2.8.0/go.mod
h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
-github.com/gogo/protobuf v1.3.2/go.mod
h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/google/go-cmp v0.5.9/go.mod
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/gofuzz v1.0.0/go.mod
h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/json-iterator/go v1.1.12
h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
-github.com/json-iterator/go v1.1.12/go.mod
h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/kisielk/errcheck v1.5.0/go.mod
h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod
h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod
h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.2
h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
-github.com/modern-go/reflect2 v1.0.2/go.mod
h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.13.1
h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod
h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/stretchr/objx v0.1.0/go.mod
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.10.0
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
-github.com/x448/float16 v0.8.4/go.mod
h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
-github.com/yuin/goldmark v1.1.27/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod
h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod
h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod
h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod
h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod
h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
-golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
-golang.org/x/text v0.37.0/go.mod
h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod
h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod
h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
-k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
-k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
-k8s.io/apimachinery v0.33.4/go.mod
h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
-k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
-k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
-k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
-k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod
h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
-sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
-sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod
h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
-sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod
h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
-sigs.k8s.io/randfill v1.0.0/go.mod
h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v4 v4.7.0
h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
-sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod
h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
-sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
-sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/script/validator/validator.go b/script/validator/validator.go
deleted file mode 100644
index 2f9b464ca..000000000
--- a/script/validator/validator.go
+++ /dev/null
@@ -1,506 +0,0 @@
-/*
- * 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 main
-
-import (
- "fmt"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "sort"
- "strings"
-
- camelapiv1 "github.com/apache/camel-kamelets/crds/pkg/apis/camel/v1"
- "github.com/bbalet/stopwords"
- perrors "github.com/pkg/errors"
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/util/json"
- "k8s.io/apimachinery/pkg/util/yaml"
-)
-
-var (
- paramRegexp =
regexp.MustCompile(`{{[?]?([A-Za-z0-9-._]+)(?:[:][^}]*)?}}`)
-)
-
-func main() {
- if len(os.Args) != 2 {
- println("usage: validator kamelets-path")
- os.Exit(1)
- }
-
- dir := os.Args[1]
-
- kamelets := listKamelets(dir)
-
- if len(kamelets) <= 0 {
- fmt.Printf("ERROR: directory %s has no Kamelets\n", dir)
- os.Exit(1)
- }
-
- errors := verifyFileNames(kamelets)
- errors = append(errors, verifyKameletType(kamelets)...)
- errors = append(errors, verifyParameters(kamelets)...)
- errors = append(errors, verifyInvalidContent(kamelets)...)
-
- // Any failing validation above may result in error in the below
methods,
- // let's show the errors if any
- for _, err := range errors {
- fmt.Printf("ERROR: %v\n", err)
- }
- if len(errors) > 0 {
- os.Exit(1)
- }
-
- errors = append(errors, verifyAnnotations(kamelets)...)
- errors = append(errors, verifyDescriptors(kamelets)...)
- errors = append(errors, verifyDuplicates(kamelets)...)
- errors = append(errors, verifyUsedParams(kamelets)...)
- errors = append(errors, verifyTemplateConvention(kamelets)...)
- errors = append(errors, verifyDependencies(kamelets)...)
- errors = append(errors, verifyCatalogVersion(kamelets)...)
-
- for _, err := range errors {
- fmt.Printf("ERROR: %v\n", err)
- }
- if len(errors) > 0 {
- os.Exit(1)
- }
-}
-
-func verifyDuplicates(kamelets []KameletInfo) (errors []error) {
- usedTitles := make(map[string]bool)
- usedDescriptions := make(map[string]bool)
- for _, kamelet := range kamelets {
- if kamelet.Spec.Definition == nil {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the JSON schema definition", kamelet.Name))
- continue
- }
- title := kamelet.Kamelet.Spec.Definition.Title
- if _, found := usedTitles[title]; found {
- errors = append(errors, fmt.Errorf("kamelet %q has
duplicate title %q", kamelet.Name, title))
- }
- description := kamelet.Kamelet.Spec.Definition.Description
- if _, found := usedDescriptions[description]; found {
- errors = append(errors, fmt.Errorf("kamelet %q has
duplicate description %q", kamelet.Name, description))
- }
- usedTitles[title] = true
- usedDescriptions[description] = true
- }
- return errors
-}
-
-func verifyDescriptors(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- if kamelet.Spec.Definition == nil {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the JSON schema definition", kamelet.Name))
- continue
- }
- for k, p := range kamelet.Spec.Definition.Properties {
- credDescriptor := "urn:camel:group:credentials"
- if p.Format == "password" && !hasXDescriptor(p,
credDescriptor) {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q has \"password\" format but misses descriptor %q", k,
kamelet.Name, credDescriptor))
- }
- deprecatedDescriptorPrefix :=
"urn:alm:descriptor:com.tectonic.ui"
- if hasXDescriptorPrefix(p, deprecatedDescriptorPrefix) {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q uses the deprecated x-descriptor prefix %q; use %q instead", k,
kamelet.Name, deprecatedDescriptorPrefix, credDescriptor))
- }
- seenDescriptors := make(map[string]bool)
- for _, d := range p.XDescriptors {
- if seenDescriptors[d] {
- errors = append(errors,
fmt.Errorf("property %q in kamelet %q lists the x-descriptor %q more than
once", k, kamelet.Name, d))
- }
- seenDescriptors[d] = true
- }
- }
- }
- return errors
-}
-
-// verifyTemplateConvention enforces the routing convention documented in the
-// catalog README: a source Kamelet sends to "kamelet:sink" and a sink Kamelet
-// consumes from "kamelet:source". Action Kamelets are not constrained here.
-func verifyTemplateConvention(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- if kamelet.Spec.Template == nil {
- continue
- }
- template := string(kamelet.Spec.Template.RawMessage)
- switch kamelet.Labels["camel.apache.org/kamelet.type"] {
- case "source":
- if !strings.Contains(template, "kamelet:sink") {
- errors = append(errors, fmt.Errorf("source
kamelet %q must route to \"kamelet:sink\"", kamelet.Name))
- }
- case "sink":
- if !strings.Contains(template, "kamelet:source") {
- errors = append(errors, fmt.Errorf("sink
kamelet %q must consume from \"kamelet:source\"", kamelet.Name))
- }
- }
- }
- return errors
-}
-
-// verifyDependencies enforces the dependency syntax documented in the catalog
-// README: every spec.dependencies entry must be a "camel:<component>",
-// "mvn:group:artifact:version" or "github:apache/..." reference.
-func verifyDependencies(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- for _, dep := range kamelet.Spec.Dependencies {
- if !strings.HasPrefix(dep, "camel:") &&
- !strings.HasPrefix(dep, "mvn:") &&
- !strings.HasPrefix(dep, "github:apache/") {
- errors = append(errors, fmt.Errorf("kamelet %q
declares dependency %q that is not a \"camel:\", \"mvn:group:artifact:version\"
or \"github:apache/...\" reference", kamelet.Name, dep))
- }
- }
- }
- return errors
-}
-
-// verifyCatalogVersion checks that every Kamelet shares the same
-// camel.apache.org/catalog.version annotation, catching a Kamelet that was
-// added or edited without bumping the catalog version.
-func verifyCatalogVersion(kamelets []KameletInfo) (errors []error) {
- const versionAnnotation = "camel.apache.org/catalog.version"
- versions := make(map[string][]string)
- for _, kamelet := range kamelets {
- v := kamelet.Annotations[versionAnnotation]
- versions[v] = append(versions[v], kamelet.Name)
- }
- if len(versions) > 1 {
- summary := make([]string, 0, len(versions))
- for v, names := range versions {
- summary = append(summary, fmt.Sprintf("%q (%d
kamelets)", v, len(names)))
- }
- sort.Strings(summary)
- errors = append(errors, fmt.Errorf("inconsistent %s across the
catalog: %s", versionAnnotation, strings.Join(summary, ", ")))
- }
- return errors
-}
-
-func hasXDescriptor(p camelapiv1.JSONSchemaProp, desc string) bool {
- for _, d := range p.XDescriptors {
- if d == desc {
- return true
- }
- }
- return false
-}
-
-func hasXDescriptorPrefix(p camelapiv1.JSONSchemaProp, prefix string) bool {
- for _, d := range p.XDescriptors {
- if strings.HasPrefix(d, prefix) {
- return true
- }
- }
- return false
-}
-
-func verifyInvalidContent(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- ser, err := json.Marshal(&kamelet.Kamelet)
- if err != nil {
- errors = append(errors, perrors.Wrapf(err, "cannot
marshal kamelet %q", kamelet.Name))
- continue
- }
- var unstr unstructured.Unstructured
- err = json.Unmarshal(ser, &unstr)
- if err != nil {
- errors = append(errors, perrors.Wrapf(err, "cannot
unmarshal kamelet %q", kamelet.Name))
- continue
- }
-
- file, err := os.ReadFile(kamelet.FileName)
- if err != nil {
- errors = append(errors, perrors.Wrapf(err, "cannot load
kamelet %q", kamelet.Name))
- continue
- }
- var yamlFile map[string]interface{}
- err = yaml.Unmarshal(file, &yamlFile)
- if err != nil {
- errors = append(errors, perrors.Wrapf(err, "kamelet %q
is not a valid YAML file", kamelet.Name))
- continue
- }
- }
- return errors
-}
-
-func verifyParameters(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- if kamelet.Spec.Definition == nil {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the JSON schema definition", kamelet.Name))
- continue
- }
- if kamelet.Spec.Template == nil {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the Template specification", kamelet.Name))
- continue
- }
- requiredCheck := make(map[string]bool)
- for _, p := range kamelet.Spec.Definition.Required {
- if requiredCheck[p] {
- errors = append(errors, fmt.Errorf("required
kamelet property %q is listed twice in kamelet %q", p, kamelet.Name))
- }
- requiredCheck[p] = true
- }
- if kamelet.Spec.Definition.Title == "" {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain title", kamelet.Name))
- } else {
- tp := kamelet.Labels["camel.apache.org/kamelet.type"]
- if len(tp) > 1 {
- expectedSuffix := strings.ToUpper(tp[0:1]) +
tp[1:]
- if
!strings.HasSuffix(kamelet.Spec.Definition.Title, expectedSuffix) {
- errors = append(errors,
fmt.Errorf("kamelet %q title %q does not ends with %q", kamelet.Name,
kamelet.Spec.Definition.Title, expectedSuffix))
- }
- }
- }
- if kamelet.Spec.Definition.Description == "" {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain description", kamelet.Name))
- }
- if kamelet.Spec.Definition.Type != "object" {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain a definition of type \"object\"", kamelet.Name))
- }
- for k, p := range kamelet.Spec.Definition.Properties {
- if p.Type == "" {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q does not contain type", k, kamelet.Name))
- }
- if p.Title == "" {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q does not contain title", k, kamelet.Name))
- } else {
- cleanTitle := stopwords.CleanString(p.Title,
"en", false)
- goodWords := strings.Split(cleanTitle, " ")
- check := make(map[string]bool, len(goodWords))
- for _, w := range goodWords {
- check[strings.ToLower(w)] = true
- }
- words := strings.Split(p.Title, " ")
- for _, w := range words {
- if !check[strings.ToLower(w)] {
- continue
- }
- if len(w) > 0 &&
strings.ToUpper(w[0:1]) != w[0:1] {
- errors = append(errors,
fmt.Errorf("property %q in kamelet %q does has non-capitalized word in the
title: %q", k, kamelet.Name, w))
- }
- }
- }
- if strings.HasPrefix(p.Title, "The ") {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q has a title starting with \"The \"", k, kamelet.Name))
- }
- if strings.HasPrefix(p.Title, "A ") {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q has a title starting with \"A \"", k, kamelet.Name))
- }
- if strings.HasPrefix(p.Title, "An ") {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q has a title starting with \"An \"", k, kamelet.Name))
- }
- if p.Description == "" {
- errors = append(errors, fmt.Errorf("property %q
in kamelet %q does not contain a description", k, kamelet.Name))
- }
- }
- for _, k := range kamelet.Spec.Definition.Required {
- if _, ok := kamelet.Spec.Definition.Properties[k]; !ok {
- errors = append(errors, fmt.Errorf("required
property %q in kamelet %q is not defined", k, kamelet.Name))
- }
- }
- }
- return errors
-}
-
-func verifyAnnotations(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- if icon :=
kamelet.Annotations["camel.apache.org/kamelet.icon"]; icon == "" {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the camel.apache.org/kamelet.icon annotation", kamelet.Name))
- }
- if catalogVersion :=
kamelet.Annotations["camel.apache.org/catalog.version"]; catalogVersion == "" {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the camel.apache.org/catalog.version annotation (should match project
version in pom.xml)", kamelet.Name))
- }
- expectedProvider := "Apache Software Foundation"
- if provider :=
kamelet.Annotations["camel.apache.org/provider"]; provider != expectedProvider {
- errors = append(errors, fmt.Errorf("kamelet %q does not
contain the right value for the camel.apache.org/provider annotation: expected
%q, found %q", kamelet.Name, expectedProvider, provider))
- }
- }
- return errors
-}
-
-func verifyKameletType(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- tp := kamelet.Labels["camel.apache.org/kamelet.type"]
- switch tp {
- case "source":
- fallthrough
- case "sink":
- fallthrough
- case "action":
- expectedSuffix := fmt.Sprintf("-%s", tp)
- if !strings.HasSuffix(kamelet.Name, expectedSuffix) {
- errors = append(errors, fmt.Errorf("name of
kamelet %q does not end with %q", kamelet.Name, expectedSuffix))
- }
- default:
- errors = append(errors, fmt.Errorf("kamelet %q contains
an invalid value for the camel.apache.org/kamelet.type label: %q",
kamelet.Name, tp))
- }
- }
- return errors
-}
-
-func verifyFileNames(kamelets []KameletInfo) (errors []error) {
- for _, kamelet := range kamelets {
- if kamelet.Name !=
strings.TrimSuffix(path.Base(kamelet.FileName), ".kamelet.yaml") {
- errors = append(errors, fmt.Errorf("file %q does not
match the name of the contained kamelet: %q", kamelet.FileName, kamelet.Name))
- }
- }
- return errors
-}
-
-func listKamelets(dir string) []KameletInfo {
- scheme := runtime.NewScheme()
- err := camelapiv1.AddToScheme(scheme)
- handleGeneralError("cannot to add camel APIs to scheme", err)
-
- kamelets := make([]KameletInfo, 0)
- files, err := os.ReadDir(dir)
- filesSorted := make([]string, 0)
- handleGeneralError(fmt.Sprintf("cannot list dir %q", dir), err)
- for _, fd := range files {
- if !fd.IsDir() && strings.HasSuffix(fd.Name(), ".kamelet.yaml")
{
- fullName := filepath.Join(dir, fd.Name())
- filesSorted = append(filesSorted, fullName)
- }
- }
- sort.Strings(filesSorted)
- for _, fileName := range filesSorted {
- content, err := os.ReadFile(fileName)
- handleGeneralError(fmt.Sprintf("cannot read file %q",
fileName), err)
- var kamelet *camelapiv1.Kamelet
- err = yaml.Unmarshal(content, &kamelet)
- handleGeneralError(fmt.Sprintf("cannot unmarshal file %q into
Kamelet", fileName), err)
- kameletInfo := KameletInfo{
- Kamelet: *kamelet,
- FileName: fileName,
- }
- kamelets = append(kamelets, kameletInfo)
- }
- return kamelets
-}
-
-func verifyUsedParams(kamelets []KameletInfo) (errors []error) {
- for _, k := range kamelets {
- if k.FileName !=
"../../kamelets/azure-storage-blob-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/aws-s3-event-based-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/ceph-event-based-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/aws-sqs-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/set-kafka-key-action.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/azure-storage-blob-event-based-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/google-storage-event-based-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/elasticsearch-search-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/opensearch-search-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/kafka-azure-schema-registry-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/kafka-azure-schema-registry-sink.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/kafka-batch-azure-schema-registry-source.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/cassandra-sink.kamelet.yaml" &&
- k.FileName !=
"../../kamelets/data-type-action.kamelet.yaml" {
- used := getUsedParams(k.Kamelet)
- declared := getDeclaredParams(k.Kamelet)
- for p := range used {
- if _, ok := declared[p]; !ok {
- errors = append(errors,
fmt.Errorf("parameter %q is not declared in the definition of kamelet %q", p,
k.Kamelet.Name))
- }
- }
- for p := range declared {
- if _, ok := used[p]; !ok {
- errors = append(errors,
fmt.Errorf("parameter %q is declared in kamelet %q but never used", p,
k.Kamelet.Name))
- }
- }
- }
- }
- return errors
-}
-
-func getDeclaredParams(k camelapiv1.Kamelet) map[string]bool {
- res := make(map[string]bool)
- if k.Spec.Definition != nil {
- for p := range k.Spec.Definition.Properties {
- res[p] = true
- }
- }
- // include beans
- var templateData interface{}
- if err := json.Unmarshal(k.Spec.Template.RawMessage, &templateData);
err != nil {
- handleGeneralError("cannot unmarshal template", err)
- }
- if fd, ok := templateData.(map[string]interface{}); ok {
- beans := fd["beans"]
- if bl, ok := beans.([]interface{}); ok {
- for _, bdef := range bl {
- if bmap, ok := bdef.(map[string]interface{});
ok {
- beanName := bmap["name"]
- if beanNameStr, ok :=
beanName.(string); ok {
- res[beanNameStr] = true
- }
- }
- }
- }
- }
- return res
-}
-
-func getUsedParams(k camelapiv1.Kamelet) map[string]bool {
- if k.Spec.Template != nil {
- var templateData interface{}
- if err := json.Unmarshal(k.Spec.Template.RawMessage,
&templateData); err != nil {
- handleGeneralError("cannot unmarshal template", err)
- }
- params := make(map[string]bool)
- inspectTemplateParams(templateData, params)
- return params
- }
- return nil
-}
-
-func inspectTemplateParams(v interface{}, params map[string]bool) {
- switch val := v.(type) {
- case string:
- res := paramRegexp.FindAllStringSubmatch(val, -1)
- for _, m := range res {
- if len(m) > 1 {
- params[m[1]] = true
- }
- }
- case []interface{}:
- for _, c := range val {
- inspectTemplateParams(c, params)
- }
- case map[string]interface{}:
- for _, c := range val {
- inspectTemplateParams(c, params)
- }
- case map[interface{}]interface{}:
- for _, c := range val {
- inspectTemplateParams(c, params)
- }
- }
-}
-
-type KameletInfo struct {
- camelapiv1.Kamelet
- FileName string
-}
-
-func handleGeneralError(desc string, err error) {
- if err != nil {
- fmt.Printf("%s: %+v\n", desc, err)
- os.Exit(2)
- }
-}