Repository: apache/camel-k
Pull request #4340 (2023-05-08 12:49:07)

[Backport 1.12.x] Always uninstall integration platform
Manual backport of #4334



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 193a96234..8e733d681 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
 **Closed issues:**
 
 - Native "higher memory" checks not running [\#4328](https://github.com/apache/camel-k/issues/4328)
+- TestKamelReset test flakiness [\#4316](https://github.com/apache/camel-k/issues/4316)
 - Cannot set GOMAXPROCS [\#4299](https://github.com/apache/camel-k/issues/4299)
 - Maximum number of running builds \(0\) exceeded [\#4290](https://github.com/apache/camel-k/issues/4290)
 - Should uninstall --all remove PersistentVolumeClaim? [\#4285](https://github.com/apache/camel-k/issues/4285)
@@ -15,6 +16,7 @@
 - Camel-k install and uninstall on a fresh kind cluster in Linux [\#4277](https://github.com/apache/camel-k/issues/4277)
 - Kamelet redis-source not working [\#4276](https://github.com/apache/camel-k/issues/4276)
 - Failed to fetch data from Mysql database using camel-k [\#4264](https://github.com/apache/camel-k/issues/4264)
+- Mention the published "generated extension" for Java in the docs [\#4262](https://github.com/apache/camel-k/issues/4262)
 - unable to access the URL and unable to expose port after the integration build is successfully deployed. [\#4256](https://github.com/apache/camel-k/issues/4256)
 - unable to execute "kamel run" in secure mode [\#4249](https://github.com/apache/camel-k/issues/4249)
 - Acquiring lock at startup is taking long [\#4246](https://github.com/apache/camel-k/issues/4246)
@@ -46,8 +48,10 @@
 - Bring in startup probes to the Health trait [\#4146](https://github.com/apache/camel-k/issues/4146)
 - Bring an option to force an image build to the kamel run command [\#4126](https://github.com/apache/camel-k/issues/4126)
 - How to use JVM classpaths in Native mode? [\#4124](https://github.com/apache/camel-k/issues/4124)
+- Secret managers parsing errors [\#4080](https://github.com/apache/camel-k/issues/4080)
 - Remove deprecated linters [\#4061](https://github.com/apache/camel-k/issues/4061)
 - Remove github.com/fatih/structs dependency [\#4033](https://github.com/apache/camel-k/issues/4033)
+- Bump Mandrel from 22.2.0 to 22.3.1 [\#4017](https://github.com/apache/camel-k/issues/4017)
 - Remove dependency no longer maintained [\#3935](https://github.com/apache/camel-k/issues/3935)
 - Expose runtime details in `kamel version -a -v` [\#3836](https://github.com/apache/camel-k/issues/3836)
 - Remove KEP cryptic message when installing [\#3829](https://github.com/apache/camel-k/issues/3829)
diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go
index 7ff9279a6..f5f5aaf33 100644
--- a/pkg/cmd/uninstall.go
+++ b/pkg/cmd/uninstall.go
@@ -417,7 +417,7 @@ func (o *uninstallCmdOptions) uninstallServiceAccounts(ctx context.Context, c cl
 }
 
 func (o *uninstallCmdOptions) uninstallIntegrationPlatform(ctx context.Context, c client.Client) error {
-	integrationPlatforms, err := c.CamelV1().IntegrationPlatforms(o.Namespace).List(ctx, defaultListOptions)
+	integrationPlatforms, err := c.CamelV1().IntegrationPlatforms(o.Namespace).List(ctx, metav1.ListOptions{})
 	if err != nil {
 		return err
 	}
diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go
index 823f2bd7d..730592ba2 100644
--- a/pkg/platform/defaults.go
+++ b/pkg/platform/defaults.go
@@ -53,9 +53,14 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 	// Reset the state to initial values
 	p.ResyncStatusFullConfig()
 
+	// Apply settings from global integration platform that is bound to this operator
+	if err := applyGlobalPlatformDefaults(ctx, c, p); err != nil {
+		return err
+	}
+
 	// update missing fields in the resource
 	if p.Status.Cluster == "" {
-		log.Debugf("Integration Platform [%s]: setting cluster status", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting cluster status", p.Name, p.Namespace)
 		// determine the kind of cluster the platform is installed into
 		isOpenShift, err := openshift.IsOpenShift(c)
 		switch {
@@ -69,7 +74,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 	}
 
 	if p.Status.Build.PublishStrategy == "" {
-		log.Debugf("Integration Platform [%s]: setting publishing strategy", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting publishing strategy %s", p.Name, p.Namespace, p.Status.Build.PublishStrategy)
 		if p.Status.Cluster == v1.IntegrationPlatformClusterOpenShift {
 			p.Status.Build.PublishStrategy = v1.IntegrationPlatformBuildPublishStrategyS2I
 		} else {
@@ -78,7 +83,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 	}
 
 	if p.Status.Build.BuildStrategy == "" {
-		log.Debugf("Integration Platform [%s]: setting build strategy", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting build strategy %s", p.Name, p.Namespace, p.Status.Build.BuildStrategy)
 		// Use the fastest strategy that they support (routine when possible)
 		if p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyS2I ||
 			p.Status.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategySpectrum {
@@ -117,7 +122,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl
 }
 
 func CreateBuilderServiceAccount(ctx context.Context, client client.Client, p *v1.IntegrationPlatform) error {
-	log.Debugf("Integration Platform [%s]: creating build service account", p.Namespace)
+	log.Debugf("Integration Platform %s [%s]: creating build service account", p.Name, p.Namespace)
 	sa := corev1.ServiceAccount{}
 	key := ctrl.ObjectKey{
 		Name:      BuilderServiceAccount,
@@ -136,7 +141,7 @@ func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPl
 	if p.Status.Cluster == v1.IntegrationPlatformClusterOpenShift &&
 		p.Status.Build.PublishStrategy != v1.IntegrationPlatformBuildPublishStrategyS2I &&
 		p.Status.Build.Registry.Address == "" {
-		log.Debugf("Integration Platform [%s]: setting registry address", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting registry address", p.Name, p.Namespace)
 		// Default to using OpenShift internal container images registry when using a strategy other than S2I
 		p.Status.Build.Registry.Address = "image-registry.openshift-image-registry.svc:5000"
 
@@ -145,12 +150,12 @@ func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPl
 		if err != nil {
 			return err
 		}
-		log.Debugf("Integration Platform [%s]: setting registry certificate authority", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting registry certificate authority", p.Name, p.Namespace)
 		p.Status.Build.Registry.CA = cm.Name
 
 		// Default to using the registry secret that's configured for the builder service account
 		if p.Status.Build.Registry.Secret == "" {
-			log.Debugf("Integration Platform [%s]: setting registry secret", p.Namespace)
+			log.Debugf("Integration Platform %s [%s]: setting registry secret", p.Name, p.Namespace)
 			// Bind the required role to push images to the registry
 			err := createBuilderRegistryRoleBinding(ctx, c, p)
 			if err != nil {
@@ -186,25 +191,136 @@ func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPl
 	return nil
 }
 
+func applyGlobalPlatformDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPlatform) error {
+	operatorNamespace := GetOperatorNamespace()
+	if operatorNamespace != "" && operatorNamespace != p.Namespace {
+		operatorID := defaults.OperatorID()
+		if operatorID != "" {
+			if globalPlatform, err := get(ctx, c, operatorNamespace, operatorID); err != nil && !k8serrors.IsNotFound(err) {
+				return err
+			} else if globalPlatform != nil {
+				applyPlatformSpec(globalPlatform, p)
+				return nil
+			}
+		}
+
+		if globalPlatform, err := findLocal(ctx, c, operatorNamespace, true); err != nil && !k8serrors.IsNotFound(err) {
+			return err
+		} else if globalPlatform != nil {
+			applyPlatformSpec(globalPlatform, p)
+		}
+	}
+
+	return nil
+}
+
+func applyPlatformSpec(source *v1.IntegrationPlatform, target *v1.IntegrationPlatform) {
+	if target.Status.Cluster == "" {
+		target.Status.Cluster = source.Status.Cluster
+	}
+	if target.Status.Profile == "" {
+		log.Debugf("Integration Platform %s [%s]: setting profile", target.Name, target.Namespace)
+		target.Status.Profile = source.Status.Profile
+	}
+
+	if target.Status.Build.PublishStrategy == "" {
+		target.Status.Build.PublishStrategy = source.Status.Build.PublishStrategy
+	}
+	if target.Status.Build.PublishStrategyOptions == nil {
+		log.Debugf("Integration Platform %s [%s]: setting publish strategy options", target.Name, target.Namespace)
+		target.Status.Build.PublishStrategyOptions = source.Status.Build.PublishStrategyOptions
+	}
+	if target.Status.Build.BuildStrategy == "" {
+		target.Status.Build.BuildStrategy = source.Status.Build.BuildStrategy
+	}
+
+	if target.Status.Build.RuntimeVersion == "" {
+		log.Debugf("Integration Platform %s [%s]: setting runtime version", target.Name, target.Namespace)
+		target.Status.Build.RuntimeVersion = source.Status.Build.RuntimeVersion
+	}
+	if target.Status.Build.BaseImage == "" {
+		log.Debugf("Integration Platform %s [%s]: setting base image", target.Name, target.Namespace)
+		target.Status.Build.BaseImage = source.Status.Build.BaseImage
+	}
+
+	if target.Status.Build.Maven.LocalRepository == "" {
+		log.Debugf("Integration Platform %s [%s]: setting local repository", target.Name, target.Namespace)
+		target.Status.Build.Maven.LocalRepository = source.Status.Build.Maven.LocalRepository
+	}
+
+	if len(source.Status.Build.Maven.CLIOptions) > 0 && len(target.Status.Build.Maven.CLIOptions) == 0 {
+		log.Debugf("Integration Platform %s [%s]: setting CLI options", target.Name, target.Namespace)
+		target.Status.Build.Maven.CLIOptions = make([]string, len(source.Status.Build.Maven.CLIOptions))
+		copy(target.Status.Build.Maven.CLIOptions, source.Status.Build.Maven.CLIOptions)
+	}
+
+	if len(source.Status.Build.Maven.Properties) > 0 {
+		log.Debugf("Integration Platform %s [%s]: setting Maven properties", target.Name, target.Namespace)
+		if len(target.Status.Build.Maven.Properties) == 0 {
+			target.Status.Build.Maven.Properties = make(map[string]string, len(source.Status.Build.Maven.Properties))
+		}
+
+		for key, val := range source.Status.Build.Maven.Properties {
+			// only set unknown properties on target
+			if _, ok := target.Status.Build.Maven.Properties[key]; !ok {
+				target.Status.Build.Maven.Properties[key] = val
+			}
+		}
+	}
+
+	if len(source.Status.Build.Maven.Extension) > 0 && len(target.Status.Build.Maven.Extension) == 0 {
+		log.Debugf("Integration Platform %s [%s]: setting Maven extensions", target.Name, target.Namespace)
+		target.Status.Build.Maven.Extension = make([]v1.MavenArtifact, len(source.Status.Build.Maven.Extension))
+		copy(target.Status.Build.Maven.Extension, source.Status.Build.Maven.Extension)
+	}
+
+	if target.Status.Build.Registry.Address == "" && source.Status.Build.Registry.Address != "" {
+		log.Debugf("Integration Platform %s [%s]: setting registry", target.Name, target.Namespace)
+		source.Status.Build.Registry.DeepCopyInto(&target.Status.Build.Registry)
+	}
+
+	if err := target.Status.Traits.Merge(source.Status.Traits); err != nil {
+		log.Errorf(err, "Integration Platform %s [%s]: failed to merge traits", target.Name, target.Namespace)
+	} else if err := target.Status.Traits.Merge(target.Spec.Traits); err != nil {
+		log.Errorf(err, "Integration Platform %s [%s]: failed to merge traits", target.Name, target.Namespace)
+	}
+
+	// Build timeout
+	if target.Status.Build.Timeout == nil {
+		log.Debugf("Integration Platform %s [%s]: setting build timeout", target.Name, target.Namespace)
+		target.Status.Build.Timeout = source.Status.Build.Timeout
+	}
+
+	if target.Status.Build.MaxRunningBuilds <= 0 {
+		log.Debugf("Integration Platform %s [%s]: setting max running builds", target.Name, target.Namespace)
+		target.Status.Build.MaxRunningBuilds = source.Status.Build.MaxRunningBuilds
+	}
+
+	if len(target.Status.Kamelet.Repositories) == 0 {
+		log.Debugf("Integration Platform %s [%s]: setting kamelet repositories", target.Name, target.Namespace)
+		target.Status.Kamelet.Repositories = source.Status.Kamelet.Repositories
+	}
+}
+
 func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 	if p.Status.Build.PublishStrategyOptions == nil {
-		log.Debugf("Integration Platform [%s]: setting publish strategy options", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting publish strategy options", p.Name, p.Namespace)
 		p.Status.Build.PublishStrategyOptions = map[string]string{}
 	}
 	if p.Status.Build.RuntimeVersion == "" {
-		log.Debugf("Integration Platform [%s]: setting runtime version", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting runtime version", p.Name, p.Namespace)
 		p.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
 	}
 	if p.Status.Build.BaseImage == "" {
-		log.Debugf("Integration Platform [%s]: setting base image", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting base image", p.Name, p.Namespace)
 		p.Status.Build.BaseImage = defaults.BaseImage()
 	}
 	if p.Status.Build.Maven.LocalRepository == "" {
-		log.Debugf("Integration Platform [%s]: setting local repository", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting local repository", p.Name, p.Namespace)
 		p.Status.Build.Maven.LocalRepository = defaults.LocalRepository
 	}
 	if len(p.Status.Build.Maven.CLIOptions) == 0 {
-		log.Debugf("Integration Platform [%s]: setting CLI options", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting CLI options", p.Name, p.Namespace)
 		p.Status.Build.Maven.CLIOptions = []string{
 			"-V",
 			"--no-transfer-progress",
@@ -212,7 +328,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 		}
 	}
 	if _, ok := p.Status.Build.PublishStrategyOptions[builder.KanikoPVCName]; !ok {
-		log.Debugf("Integration Platform [%s]: setting publish strategy options", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting publish strategy options", p.Name, p.Namespace)
 		p.Status.Build.PublishStrategyOptions[builder.KanikoPVCName] = p.Name
 	}
 
@@ -223,7 +339,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 			log.Log.Infof("Build timeout minimum unit is sec (configured: %s, truncated: %s)", p.Status.Build.GetTimeout().Duration, d)
 		}
 
-		log.Debugf("Integration Platform [%s]: setting build timeout", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting build timeout", p.Name, p.Namespace)
 		p.Status.Build.Timeout = &metav1.Duration{
 			Duration: d,
 		}
@@ -235,6 +351,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 	}
 
 	if p.Status.Build.MaxRunningBuilds <= 0 {
+		log.Debugf("Integration Platform %s [%s]: setting max running builds", p.Name, p.Namespace)
 		if p.Status.Build.BuildStrategy == v1.BuildStrategyRoutine {
 			p.Status.Build.MaxRunningBuilds = 3
 		} else if p.Status.Build.BuildStrategy == v1.BuildStrategyPod {
@@ -255,7 +372,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 	}
 
 	if len(p.Status.Kamelet.Repositories) == 0 {
-		log.Debugf("Integration Platform [%s]: setting kamelet repositories", p.Namespace)
+		log.Debugf("Integration Platform %s [%s]: setting kamelet repositories", p.Name, p.Namespace)
 		p.Status.Kamelet.Repositories = append(p.Status.Kamelet.Repositories, v1.IntegrationPlatformKameletRepositorySpec{
 			URI: repository.DefaultRemoteRepository,
 		})
@@ -275,13 +392,13 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error {
 func setStatusAdditionalInfo(platform *v1.IntegrationPlatform) {
 	platform.Status.Info = make(map[string]string)
 
-	log.Debugf("Integration Platform [%s]: setting build publish strategy", platform.Namespace)
+	log.Debugf("Integration Platform %s [%s]: setting build publish strategy", platform.Name, platform.Namespace)
 	if platform.Spec.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyBuildah {
 		platform.Status.Info["buildahVersion"] = defaults.BuildahVersion
 	} else if platform.Spec.Build.PublishStrategy == v1.IntegrationPlatformBuildPublishStrategyKaniko {
 		platform.Status.Info["kanikoVersion"] = defaults.KanikoVersion
 	}
-	log.Debugf("Integration Platform [%s]: setting status info", platform.Namespace)
+	log.Debugf("Integration Platform %s [%s]: setting status info", platform.Name, platform.Namespace)
 	platform.Status.Info["goVersion"] = runtime.Version()
 	platform.Status.Info["goOS"] = runtime.GOOS
 	platform.Status.Info["gitCommit"] = defaults.GitCommit


Test patch:
diff --git a/e2e/global/common/local_platform_test.go b/e2e/global/common/local_platform_test.go
new file mode 100644
index 000000000..86f9799aa
--- /dev/null
+++ b/e2e/global/common/local_platform_test.go
@@ -0,0 +1,103 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 common
+
+import (
+	"testing"
+
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+
+	. "github.com/apache/camel-k/e2e/support"
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	traitv1 "github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+)
+
+func TestLocalPlatform(t *testing.T) {
+	WithNewTestNamespace(t, func(ns string) {
+		operatorID := "camel-k-platform-local"
+		Expect(KamelInstallWithID(operatorID, ns, "--global", "--force").Execute()).To(Succeed())
+		Eventually(PlatformPhase(ns), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady))
+
+		pl := Platform(ns)()
+		pl.Spec.Build.Maven.Properties = make(map[string]string)
+		pl.Spec.Build.Maven.Properties["build-global-prop1"] = "build-global-value1"
+		// set maximum number of running builds
+		pl.Spec.Build.MaxRunningBuilds = 1
+		if err := TestClient().Update(TestContext, pl); err != nil {
+			t.Error(err)
+			t.FailNow()
+		}
+
+		Eventually(PlatformHas(ns, func(pl *v1.IntegrationPlatform) bool {
+			return pl.Status.Build.MaxRunningBuilds == 1
+		}), TestTimeoutMedium).Should(BeTrue())
+
+		WithNewTestNamespace(t, func(ns1 string) {
+			localPlatform := v1.NewIntegrationPlatform(ns1, operatorID)
+			localPlatform.Spec.Build.Maven.Properties = make(map[string]string)
+			localPlatform.Spec.Build.Maven.Properties["build-local-prop1"] = "build-local-value1"
+			localPlatform.SetOperatorID(operatorID)
+
+			localPlatform.Spec.Traits.Container = &traitv1.ContainerTrait{
+				LimitCPU: "0.1",
+			}
+
+			if err := TestClient().Create(TestContext, &localPlatform); err != nil {
+				t.Error(err)
+				t.FailNow()
+			}
+			Eventually(PlatformPhase(ns1), TestTimeoutMedium).Should(Equal(v1.IntegrationPlatformPhaseReady))
+			Eventually(PlatformHas(ns1, func(pl *v1.IntegrationPlatform) bool {
+				return pl.Status.Cluster != ""
+			}), TestTimeoutShort).Should(BeTrue())
+
+			Eventually(PlatformHas(ns1, func(pl *v1.IntegrationPlatform) bool {
+				return pl.Status.Build.MaxRunningBuilds == 1
+			}), TestTimeoutShort).Should(BeTrue())
+
+			local := Platform(ns1)()
+			Expect(local.Status.Build.PublishStrategy).To(Equal(pl.Status.Build.PublishStrategy))
+			Expect(local.Status.Build.BuildStrategy).To(Equal(pl.Status.Build.BuildStrategy))
+			Expect(local.Status.Build.Maven.LocalRepository).To(Equal(pl.Status.Build.Maven.LocalRepository))
+			Expect(local.Status.Build.Maven.CLIOptions).To(ContainElements(pl.Status.Build.Maven.CLIOptions))
+			Expect(local.Status.Build.Maven.Extension).To(BeEmpty())
+			Expect(local.Status.Build.Maven.Properties).To(HaveLen(2))
+			Expect(local.Status.Build.Maven.Properties["build-global-prop1"]).To(Equal("build-global-value1"))
+			Expect(local.Status.Build.Maven.Properties["build-local-prop1"]).To(Equal("build-local-value1"))
+
+			Expect(KamelRunWithID(operatorID, ns1, "--name", "local-integration", "files/yaml.yaml").Execute()).To(Succeed())
+			Eventually(IntegrationPod(ns1, "local-integration"), TestTimeoutMedium).Should(Not(BeNil()))
+			Eventually(IntegrationPodHas(ns1, "local-integration", func(pod *corev1.Pod) bool {
+				if len(pod.Spec.Containers) != 1 {
+					return false
+				}
+				cpuLimits := pod.Spec.Containers[0].Resources.Limits.Cpu()
+				return cpuLimits != nil && cpuLimits.AsApproximateFloat64() > 0
+			}), TestTimeoutShort).Should(BeTrue())
+
+			// Clean up
+			Expect(Kamel("delete", "--all", "-n", ns1).Execute()).To(Succeed())
+		})
+	})
+}
diff --git a/e2e/support/test_support.go b/e2e/support/test_support.go
index 489734ac8..762dc0f55 100644
--- a/e2e/support/test_support.go
+++ b/e2e/support/test_support.go
@@ -1620,6 +1620,16 @@ func PlatformPhase(ns string) func() v1.IntegrationPlatformPhase {
 	}
 }
 
+func PlatformHas(ns string, predicate func(pl *v1.IntegrationPlatform) bool) func() bool {
+	return func() bool {
+		pl := Platform(ns)()
+		if pl == nil {
+			return false
+		}
+		return predicate(pl)
+	}
+}
+
 func PlatformCondition(
 	ns string,
 	conditionType v1.IntegrationPlatformConditionType,
diff --git a/pkg/platform/defaults_test.go b/pkg/platform/defaults_test.go
new file mode 100644
index 000000000..2639420d5
--- /dev/null
+++ b/pkg/platform/defaults_test.go
@@ -0,0 +1,187 @@
+/*
+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 platform
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
+	"github.com/apache/camel-k/pkg/apis/camel/v1/trait"
+	"github.com/apache/camel-k/pkg/util/test"
+)
+
+func TestApplyGlobalPlatformSpec(t *testing.T) {
+	global := v1.IntegrationPlatform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      DefaultPlatformName,
+			Namespace: "ns",
+		},
+		Spec: v1.IntegrationPlatformSpec{
+			Build: v1.IntegrationPlatformBuildSpec{
+				BuildStrategy: v1.BuildStrategyRoutine,
+				Maven: v1.MavenSpec{
+					Properties: map[string]string{
+						"global_prop1": "global_value1",
+						"global_prop2": "global_value2",
+					},
+				},
+			},
+			Traits: v1.Traits{
+				Logging: &trait.LoggingTrait{
+					Level: "DEBUG",
+				},
+				Container: &trait.ContainerTrait{
+					ImagePullPolicy: corev1.PullAlways,
+					LimitCPU:        "0.1",
+				},
+			},
+			Cluster: v1.IntegrationPlatformClusterOpenShift,
+			Profile: v1.TraitProfileOpenShift,
+		},
+	}
+
+	c, err := test.NewFakeClient(&global)
+	assert.Nil(t, err)
+
+	err = ConfigureDefaults(context.TODO(), c, &global, false)
+	assert.Nil(t, err)
+
+	ip := v1.IntegrationPlatform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "local-camel-k",
+			Namespace: "ns",
+		},
+	}
+
+	ip.ResyncStatusFullConfig()
+
+	applyPlatformSpec(&global, &ip)
+
+	assert.Equal(t, v1.IntegrationPlatformClusterOpenShift, ip.Status.Cluster)
+	assert.Equal(t, v1.TraitProfileOpenShift, ip.Status.Profile)
+	assert.Equal(t, v1.BuildStrategyRoutine, ip.Status.Build.BuildStrategy)
+
+	assert.True(t, ip.Status.Build.MaxRunningBuilds == 3) // default for build strategy routine
+
+	assert.Equal(t, len(global.Status.Build.Maven.CLIOptions), len(ip.Status.Build.Maven.CLIOptions))
+	assert.Equal(t, global.Status.Build.Maven.CLIOptions, ip.Status.Build.Maven.CLIOptions)
+
+	assert.NotNil(t, ip.Status.Traits)
+	assert.NotNil(t, ip.Status.Traits.Logging)
+	assert.Equal(t, "DEBUG", ip.Status.Traits.Logging.Level)
+	assert.NotNil(t, ip.Status.Traits.Container)
+	assert.Equal(t, corev1.PullAlways, ip.Status.Traits.Container.ImagePullPolicy)
+	assert.Equal(t, "0.1", ip.Status.Traits.Container.LimitCPU)
+
+	assert.Equal(t, 2, len(ip.Status.Build.Maven.Properties))
+	assert.Equal(t, "global_value1", ip.Status.Build.Maven.Properties["global_prop1"])
+	assert.Equal(t, "global_value2", ip.Status.Build.Maven.Properties["global_prop2"])
+}
+
+func TestRetainLocalPlatformSpec(t *testing.T) {
+	global := v1.IntegrationPlatform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      DefaultPlatformName,
+			Namespace: "ns",
+		},
+		Spec: v1.IntegrationPlatformSpec{
+			Build: v1.IntegrationPlatformBuildSpec{
+				BuildStrategy: v1.BuildStrategyRoutine,
+				Maven: v1.MavenSpec{
+					Properties: map[string]string{
+						"global_prop1": "global_value1",
+						"global_prop2": "global_value2",
+					},
+				},
+			},
+			Traits: v1.Traits{
+				Logging: &trait.LoggingTrait{
+					Level: "DEBUG",
+				},
+				Container: &trait.ContainerTrait{
+					ImagePullPolicy: corev1.PullIfNotPresent,
+					LimitCPU:        "0.1",
+				},
+			},
+			Cluster: v1.IntegrationPlatformClusterOpenShift,
+			Profile: v1.TraitProfileOpenShift,
+		},
+	}
+
+	c, err := test.NewFakeClient(&global)
+	assert.Nil(t, err)
+
+	err = ConfigureDefaults(context.TODO(), c, &global, false)
+	assert.Nil(t, err)
+
+	ip := v1.IntegrationPlatform{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "local-camel-k",
+			Namespace: "ns",
+		},
+		Spec: v1.IntegrationPlatformSpec{
+			Build: v1.IntegrationPlatformBuildSpec{
+				BuildStrategy:    v1.BuildStrategyPod,
+				MaxRunningBuilds: 1,
+				Maven: v1.MavenSpec{
+					Properties: map[string]string{
+						"local_prop1":  "local_value1",
+						"global_prop2": "local_value2",
+					},
+				},
+			},
+			Traits: v1.Traits{
+				Container: &trait.ContainerTrait{
+					ImagePullPolicy: corev1.PullAlways,
+				},
+			},
+			Cluster: v1.IntegrationPlatformClusterKubernetes,
+			Profile: v1.TraitProfileKnative,
+		},
+	}
+
+	ip.ResyncStatusFullConfig()
+
+	applyPlatformSpec(&global, &ip)
+
+	assert.Equal(t, v1.IntegrationPlatformClusterKubernetes, ip.Status.Cluster)
+	assert.Equal(t, v1.TraitProfileKnative, ip.Status.Profile)
+	assert.Equal(t, v1.BuildStrategyPod, ip.Status.Build.BuildStrategy)
+
+	assert.True(t, ip.Status.Build.MaxRunningBuilds == 1)
+
+	assert.Equal(t, len(global.Status.Build.Maven.CLIOptions), len(ip.Status.Build.Maven.CLIOptions))
+	assert.Equal(t, global.Status.Build.Maven.CLIOptions, ip.Status.Build.Maven.CLIOptions)
+
+	assert.NotNil(t, ip.Status.Traits)
+	assert.NotNil(t, ip.Status.Traits.Logging)
+	assert.Equal(t, "DEBUG", ip.Status.Traits.Logging.Level)
+	assert.NotNil(t, ip.Status.Traits.Container)
+	assert.Equal(t, corev1.PullAlways, ip.Status.Traits.Container.ImagePullPolicy)
+	assert.Equal(t, "0.1", ip.Status.Traits.Container.LimitCPU)
+
+	assert.Equal(t, 3, len(ip.Status.Build.Maven.Properties))
+	assert.Equal(t, "global_value1", ip.Status.Build.Maven.Properties["global_prop1"])
+	assert.Equal(t, "local_value2", ip.Status.Build.Maven.Properties["global_prop2"])
+	assert.Equal(t, "local_value1", ip.Status.Build.Maven.Properties["local_prop1"])
+}