Repository: apache/camel-k
Pull request #4278 (2023-04-26 08:55:00)

chore: Support platform selection on IntegrationKit
- Do only wakeup matching integration kits when waiting for selected platform


christophd: @oscerd @squakez I have added logging when skipping kits
christophd: @oscerd could you please (re)trigger the failed job? hopefully only a hiccup
oscerd: Done.
squakez: Please, rebase as we solved the github actions issue.
christophd: @squakez rebase done, could you please trigger the workflow once again 🙏 

Patch:
diff --git a/go.mod b/go.mod
index 8e5c51233..3f84c6ee4 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/apache/camel-k/v2/pkg/apis/camel v0.0.0
 	github.com/apache/camel-k/v2/pkg/client/camel v0.0.0
 	github.com/apache/camel-k/v2/pkg/kamelet/repository v0.0.0
-	github.com/container-tools/spectrum v0.6.9
+	github.com/container-tools/spectrum v0.6.10
 	github.com/evanphx/json-patch v4.12.0+incompatible
 	github.com/fatih/camelcase v1.0.0
 	github.com/fatih/structs v1.1.0
@@ -93,7 +93,7 @@ require (
 	github.com/containerd/continuity v0.1.0 // indirect
 	github.com/containerd/stargz-snapshotter/estargz v0.14.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/docker/cli v23.0.4+incompatible // indirect
+	github.com/docker/cli v23.0.5+incompatible // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
 	github.com/docker/docker v23.0.3+incompatible // indirect
 	github.com/docker/docker-credential-helpers v0.7.0 // indirect
diff --git a/go.sum b/go.sum
index 749477fb8..2012e6ed6 100644
--- a/go.sum
+++ b/go.sum
@@ -234,8 +234,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
 github.com/container-tools/go-containerregistry v0.7.1-0.20211124090132-40ccc94a466b h1:PnXGj/WjUnNI7O3AAjXIYs+8QXVRzgoxurEgVgsItrU=
 github.com/container-tools/go-containerregistry v0.7.1-0.20211124090132-40ccc94a466b/go.mod h1:6cMIl1RfryEiPzBE67OgtZdEiLWz4myqCQIiBMy3CsM=
-github.com/container-tools/spectrum v0.6.9 h1:b3kVGM7Z+ZNgj3i4v1u0sSfR/Kq6XJNe4TwZFuQ6iL8=
-github.com/container-tools/spectrum v0.6.9/go.mod h1:dGbIq03rtRodtkC5wq0OeygkUL53dhAhurB90c870c0=
+github.com/container-tools/spectrum v0.6.10 h1:2Y3gY0qixeCfW34Kij1mgtqt/Xyc2lSmEG7UIIcp7x8=
+github.com/container-tools/spectrum v0.6.10/go.mod h1:LgoHcknbRBNbs+yT43ooAT9BTdNTkFen1Foq/ppvSAs=
 github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
 github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
 github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@@ -367,8 +367,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
 github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
-github.com/docker/cli v23.0.4+incompatible h1:xClB7PsiATttDHj8ce5qvJcikiApNy7teRR1XkoBZGs=
-github.com/docker/cli v23.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE=
+github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
 github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 73708f6ec..2911e87c2 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -562,14 +562,26 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
 
 	if existing == nil {
 		err = c.Create(o.Context, integration)
+		if err != nil {
+			return nil, err
+		}
 		fmt.Fprintln(cmd.OutOrStdout(), `Integration "`+name+`" created`)
 	} else {
-		err = c.Patch(o.Context, integration, ctrl.MergeFromWithOptions(existing, ctrl.MergeFromWithOptimisticLock{}))
-		fmt.Fprintln(cmd.OutOrStdout(), `Integration "`+name+`" updated`)
-	}
+		patch := ctrl.MergeFrom(existing)
+		d, err := patch.Data(integration)
+		if err != nil {
+			return nil, err
+		}
 
-	if err != nil {
-		return nil, err
+		if string(d) == "{}" {
+			fmt.Fprintln(cmd.OutOrStdout(), `Integration "`+name+`" unchanged`)
+			return integration, nil
+		}
+		err = c.Patch(o.Context, integration, patch)
+		if err != nil {
+			return nil, err
+		}
+		fmt.Fprintln(cmd.OutOrStdout(), `Integration "`+name+`" updated`)
 	}
 
 	return integration, nil
diff --git a/pkg/controller/integrationkit/integrationkit_controller.go b/pkg/controller/integrationkit/integrationkit_controller.go
index 7ed069e96..71d917301 100644
--- a/pkg/controller/integrationkit/integrationkit_controller.go
+++ b/pkg/controller/integrationkit/integrationkit_controller.go
@@ -148,6 +148,11 @@ func add(_ context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 				}
 
 				for _, kit := range list.Items {
+					if v, ok := kit.Annotations[v1.PlatformSelectorAnnotation]; ok && v != p.Name {
+						log.Infof("Integration kit %s is waiting for selected integration platform '%s' - skip it now", kit.Name, v)
+						continue
+					}
+
 					if kit.Status.Phase == v1.IntegrationKitPhaseWaitingForPlatform {
 						log.Infof("Platform %s ready, wake-up integration kit: %s", p.Name, kit.Name)
 						requests = append(requests, reconcile.Request{
diff --git a/release.adoc b/release.adoc
index 6bb3e72d9..af67561d9 100644
--- a/release.adoc
+++ b/release.adoc
@@ -4,62 +4,7 @@ This procedure describes all the steps required to release a new version of Apac
 It is not intended to be final, but rather a working document that needs to be updated when new requirements are found or
 the technical process changes.
 
-[[releasing-camel-kamelets]]
-== Releasing Camel Kamelets
-
-The https://github.com/apache/camel-kamelets[Camel Kamelets] repository contains some artifacts that need to be pushed into Maven together
-with a Camel K release.
-
-Follow instructions on https://github.com/apache/camel-kamelets[Camel Kamelets] for the commands needed to release it.
-
-Go to https://repository.apache.org/ and close the staging repository.
-
-A URL will be generated for the repository, like: https://repository.apache.org/content/repositories/orgapachecamel-xxxx. The URL needs to be communicated during the voting process.
-
-The source code for the created tag, with sha512sum and signature need to be copied in the Apache dev SVN at https://dist.apache.org/repos/dist/dev/camel/camel-kamelets/<version>.
-
-Commit all files to the SVN.
-
-Voting can start for camel-kamelets, or a combined vote with camel-k can be started later.
-
-[[releasing-camel-k-runtime]]
-== Releasing Camel K Runtime
-
-The https://github.com/apache/camel-k-runtime[Camel K Runtime project] is not a standalone project, it's meant to be used with Camel K. For this reason, it's common to release the runtime artifacts together with a release of Camel K.
-To avoid depending on snapshots, camel-k-runtime can be also released independently.
-
-Follow the https://github.com/apache/camel/blob/main/docs/user-manual/modules/ROOT/pages/release-guide.adoc[Camel release guide] in order to setup the Maven environment and gpg for the release.
-
-To prepare a release and check problems:
-```
-./mvnw release:prepare -DdryRun -Prelease
-```
-
-Check the signatures of the files, then clean and prepare the actual release:
-
-```
-./mvnw release:clean -Prelease
-./mvnw release:prepare -Prelease
-```
-
-Then perform the release:
-
-```
-./mvnw release:perform -Prelease
-```
-
-Go to https://repository.apache.org/ and close the staging repository.
-
-A URL will be generated for the repository, like: https://repository.apache.org/content/repositories/orgapachecamel-xxxx. The URL needs to be communicated during the voting process.
-
-Now run:
-
-```
-cd release-utils/scripts/
-./upload-sources.sh <released_version> <released_version>
-```
-
-Voting can start for camel-k-runtime, or a combined vote with camel-k can be started later.
+NOTE: starting Camel K version 2, the release process of Kamelets catalog and Camel K Runtime is independent.
 
 [[releasing-camel-k-crds]]
 == Releasing Camel K CRD dependency
@@ -97,13 +42,12 @@ Releasing the Camel K main artifacts require some manual steps.
 
 First thing is to checkout main branch and sync with upstream.
 
-Next, the version of the runtime libraries to use need to be aligned in the Makefile (use the just released camel-k-runtime version if available).
+Next, the version of the runtime libraries to use need to be aligned in the Makefile.
 
 The following tasks need to be done:
 
 - Align `KAMELET_CATALOG_REPO_TAG` in Makefile to latest released tag of the camel-kamelets repository
-- Align `RUNTIME_VERSION` in Makefile to latest runtime release
-- Set `STAGING_RUNTIME_REPO` to the URL of the staging repo (if using a staged camel-k-runtime version)
+- Align `RUNTIME_VERSION` in Makefile to latest Camel K Runtime release
 - Ensure `LAST_RELEASED_VERSION` points to latest released version of Camel K
 
 ```
@@ -175,10 +119,9 @@ A docker image `camelk/camel-k:<version>` will be pushed to the staging organiza
 A combined release can be immediately tested with the following install command:
 
 ```
-kamel install --operator-image=camelk/camel-k:$CAMEL_K_VERSION --maven-repository=https://repository.apache.org/content/repositories/orgapachecamel-$APACHE_NEXUS_RUNTIME_REPO_ID
+kamel install --operator-image=camelk/camel-k:$CAMEL_K_VERSION
 ```
 
-
 [[release-notes]]
 == Release notes
 
@@ -216,8 +159,6 @@ An email should be sent to dev@camel.apache.org asking to test the staged artifa
 
 After the voting is complete with success, the artifacts can be released.
 
-Release the staging repository at: https://repository.apache.org.
-
 Republish docker image in the Apache org on Docker Hub:
 
 ```
@@ -227,12 +168,7 @@ docker tag camelk/camel-k:$VERSION apache/camel-k:$VERSION
 docker push apache/camel-k:$VERSION
 ```
 
-Artifacts committed on https://dist.apache.org/repos/dist/dev/ before the voting process need to be copied to the
-Apache dist repository on: https://dist.apache.org/repos/dist/release/camel.
-
-The same should be done for camel-kamelets and camel-k-runtime in case of combined vote.
-
-Camel-kamelets, camel-k-runtime and camel-k contains a script to promote the release from dev to release
+Artifacts committed on https://dist.apache.org/repos/dist/dev/ before the voting process need to be copied to the Apache dist repository on: https://dist.apache.org/repos/dist/release/camel.
 
 ```
 cd release-utils/scripts/
@@ -245,8 +181,7 @@ Binary files can be now released on Github, together with release notes generate
 Files need to be uploaded manually to a new Github release.
 
 Before announcing the release, a simple test should be done to verify that everything is in place (running a "Hello World" integration
-after an installation done with a simple `kamel install`).
-Do a simple final test.
+after an installation done with a simple `kamel install`). Do a simple final test.
 
 The release can be now announced to dev@camel.apache.org and users@camel.apache.org.
 


Test patch:
diff --git a/e2e/builder/build_test.go b/e2e/builder/build_test.go
index 0058e93d6..7a7d1b21d 100644
--- a/e2e/builder/build_test.go
+++ b/e2e/builder/build_test.go
@@ -182,6 +182,7 @@ func createOperator(ns string, memoryLimit string, buildTimeout string, installA
 
 	operatorID := fmt.Sprintf("camel-k-%s", ns)
 	Expect(KamelInstallWithID(operatorID, ns, args...).Execute()).To(Succeed())
+	Eventually(PlatformPhase(ns), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady))
 }
 
 func doKitBuildInNamespace(name string, ns string, testTimeout time.Duration, options kitOptions, buildPhase v1.BuildPhase, kitPhase v1.IntegrationKitPhase) {
diff --git a/e2e/commonwithcustominstall/secondary_platform_test.go b/e2e/commonwithcustominstall/secondary_platform_test.go
index c5aebb560..6cabfb305 100644
--- a/e2e/commonwithcustominstall/secondary_platform_test.go
+++ b/e2e/commonwithcustominstall/secondary_platform_test.go
@@ -38,16 +38,15 @@ func TestSecondaryPlatform(t *testing.T) {
 	WithNewTestNamespace(t, func(ns string) {
 		operatorID := "camel-k-platform-secondary"
 		Expect(KamelInstallWithID(operatorID, ns).Execute()).To(Succeed())
+		Eventually(PlatformPhase(ns), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady))
+
 		Expect(ConfigureSecondaryPlatformWith(ns, func(p *v1.IntegrationPlatform) {
 			p.Name = "secondary"
 			p.Spec.Traits.Container = &traitv1.ContainerTrait{
-				Trait: traitv1.Trait{
-					Configuration: AsTraitConfiguration(map[string]string{
-						"limitCPU": "0.1",
-					}),
-				},
+				LimitCPU: "0.1",
 			}
 		})).To(Succeed())
+		Eventually(SelectedPlatformPhase(ns, "secondary"), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady))
 
 		Expect(KamelRunWithID(operatorID, ns, "--name", "limited", "--annotation", "camel.apache.org/platform.id=secondary", "files/yaml.yaml").Execute()).To(Succeed())
 
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 57697449c..5cd425710 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -1661,6 +1661,21 @@ func Platform(ns string) func() *v1.IntegrationPlatform {
 	}
 }
 
+func PlatformByName(ns string, name string) func() *v1.IntegrationPlatform {
+	return func() *v1.IntegrationPlatform {
+		lst := v1.NewIntegrationPlatformList()
+		if err := TestClient().List(TestContext, &lst, ctrl.InNamespace(ns)); err != nil {
+			failTest(err)
+		}
+		for _, p := range lst.Items {
+			if p.Name == name {
+				return &p
+			}
+		}
+		return nil
+	}
+}
+
 func CamelCatalog(ns, name string) func() *v1.CamelCatalog {
 	return func() *v1.CamelCatalog {
 		cat := v1.CamelCatalog{}
@@ -1774,6 +1789,16 @@ func PlatformPhase(ns string) func() v1.IntegrationPlatformPhase {
 	}
 }
 
+func SelectedPlatformPhase(ns string, name string) func() v1.IntegrationPlatformPhase {
+	return func() v1.IntegrationPlatformPhase {
+		p := PlatformByName(ns, name)()
+		if p == nil {
+			return ""
+		}
+		return p.Status.Phase
+	}
+}
+
 func PlatformCondition(
 	ns string,
 	conditionType v1.IntegrationPlatformConditionType,
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 5322319c8..f31330f7f 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -22,12 +22,14 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
 	traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
 	"github.com/apache/camel-k/v2/pkg/platform"
 	"github.com/apache/camel-k/v2/pkg/trait"
+	"github.com/apache/camel-k/v2/pkg/util/defaults"
 	"github.com/apache/camel-k/v2/pkg/util/test"
 
 	"github.com/spf13/cobra"
@@ -38,6 +40,17 @@ import (
 const (
 	cmdRun            = "run"
 	integrationSource = "example.js"
+	yamlIntegration   = `# camel-k: language=yaml
+
+- from:
+    uri: "timer:yaml"
+    parameters:
+      period: "1000"
+    steps:
+      - setBody:
+          constant: "Hello Camel K from yaml"
+      - to: "log:info"
+`
 )
 
 // nolint: unparam
@@ -54,9 +67,10 @@ func initializeRunCmdOptions(t *testing.T) (*runCmdOptions, *cobra.Command, Root
 // nolint: unparam
 func initializeRunCmdOptionsWithOutput(t *testing.T) (*runCmdOptions, *cobra.Command, RootCmdOptions) {
 	t.Helper()
-
 	defaultIntegrationPlatform := v1.NewIntegrationPlatform("default", platform.DefaultPlatformName)
-	fakeClient, _ := test.NewFakeClient(&defaultIntegrationPlatform)
+	c := v1.NewCamelCatalog(defaultIntegrationPlatform.Namespace, defaults.DefaultRuntimeVersion)
+	c.Spec = v1.CamelCatalogSpec{Runtime: v1.RuntimeSpec{Provider: defaultIntegrationPlatform.Status.Build.RuntimeProvider, Version: defaultIntegrationPlatform.Status.Build.RuntimeVersion}}
+	fakeClient, _ := test.NewFakeClient(&defaultIntegrationPlatform, &c)
 
 	options, rootCmd := kamelTestPreAddCommandInitWithClient(fakeClient)
 	runCmdOptions := addTestRunCmdWithOutput(*options, rootCmd)
@@ -773,3 +787,33 @@ func TestPropertyShouldNotExpand(t *testing.T) {
 	assert.Contains(t, output, "buildProp = ${value}")
 	assert.Contains(t, output, "key = ${value}")
 }
+
+func TestRunOutput(t *testing.T) {
+	var tmpFile1 *os.File
+	var err error
+	if tmpFile1, err = os.CreateTemp("", "camel-k-*.yaml"); err != nil {
+		t.Error(err)
+	}
+	defer tmpFile1.Close()
+
+	assert.Nil(t, tmpFile1.Sync())
+	assert.Nil(t, os.WriteFile(tmpFile1.Name(), []byte(yamlIntegration), 0o400))
+
+	_, rootCmd, _ := initializeRunCmdOptionsWithOutput(t)
+	output, err := test.ExecuteCommand(rootCmd, cmdRun, tmpFile1.Name())
+	_, fileName := filepath.Split(tmpFile1.Name())
+	integrationName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" created\n", integrationName), output)
+
+	output, err = test.ExecuteCommand(rootCmd, cmdRun, tmpFile1.Name())
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" unchanged\n", integrationName), output)
+
+	assert.Nil(t, os.WriteFile(tmpFile1.Name(), []byte(strings.Replace(yamlIntegration, "Hello", "Hi", 1)), 0o400))
+	assert.Nil(t, tmpFile1.Sync())
+	output, err = test.ExecuteCommand(rootCmd, cmdRun, tmpFile1.Name())
+	assert.Nil(t, err)
+	assert.Equal(t, fmt.Sprintf("Integration \"%s\" updated\n", integrationName), output)
+
+}
diff --git a/pkg/util/test/client.go b/pkg/util/test/client.go
index adbb8eaa2..7fc121389 100644
--- a/pkg/util/test/client.go
+++ b/pkg/util/test/client.go
@@ -150,7 +150,11 @@ func (c *FakeClient) GetCurrentNamespace(kubeConfig string) (string, error) {
 
 // Patch mimicks patch for server-side apply and simply creates the obj.
 func (c *FakeClient) Patch(ctx context.Context, obj controller.Object, patch controller.Patch, opts ...controller.PatchOption) error {
-	return c.Create(ctx, obj)
+	if err := c.Create(ctx, obj); err != nil {
+		// Create fails if object already exists. Try to update it.
+		return c.Update(ctx, obj)
+	}
+	return nil
 }
 
 func (c *FakeClient) DisableAPIGroupDiscovery(group string) {