From b479dbe9e9c5cb198fc0d8e76e2138ee38ad6814 Mon Sep 17 00:00:00 2001 From: Christoph Deppisch Date: Thu, 4 May 2023 12:01:08 +0200 Subject: [PATCH] fix: Consistent integration platform settings hierarchy (backport #4317) - Make sure to apply global integration platform settings when creating namespace local platform - Always derive local platform from operator global platform settings - Global platform settings resist when the setting is not explicitly overwritten in the given user namespaced integration platform --- e2e/global/common/local_platform_test.go | 103 +++++++++++++ e2e/support/test_support.go | 20 ++- pkg/platform/defaults.go | 137 +++++++++++++++++ pkg/platform/defaults_test.go | 187 +++++++++++++++++++++++ 4 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 e2e/global/common/local_platform_test.go create mode 100644 pkg/platform/defaults_test.go diff --git a/e2e/global/common/local_platform_test.go b/e2e/global/common/local_platform_test.go new file mode 100644 index 0000000000..86f9799aa8 --- /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 f9d23f1b3e..5ad93021a9 100644 --- a/e2e/support/test_support.go +++ b/e2e/support/test_support.go @@ -91,7 +91,7 @@ const kubeConfigEnvVar = "KUBECONFIG" var TestTimeoutShort = 1 * time.Minute var TestTimeoutMedium = 5 * time.Minute -var TestTimeoutLong = 10 * time.Minute +var TestTimeoutLong = 15 * time.Minute // TestTimeoutVeryLong should be used only for testing native builds. var TestTimeoutVeryLong = 60 * time.Minute @@ -112,6 +112,7 @@ func setTestLocus(t *testing.T) { // using the test locus to error out and fail now. func failTest(err error) { if testLocus != nil { + testLocus.Helper() testLocus.Error(err) testLocus.FailNow() } else { @@ -390,9 +391,9 @@ func MakeWithContext(ctx context.Context, rule string, args ...string) *exec.Cmd return exec.Command("make", args...) } -/* - Curryied utility functions for testing -*/ +// ============================================================================= +// Curried utility functions for testing +// ============================================================================= func IntegrationLogs(ns, name string) func() string { return func() string { @@ -455,6 +456,7 @@ func StructuredLogs(ns, podName string, options corev1.PodLogOptions, ignorePars err := json.Unmarshal([]byte(t), &entry) if err != nil { if ignoreParseErrors { + fmt.Printf("Warning: Ignoring parse error for logging line: %q\n", t) continue } else { log.Errorf(err, "Unable to parse structured content: %s", t) @@ -1432,6 +1434,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 PlatformProfile(ns string) func() v1.TraitProfile { return func() v1.TraitProfile { p := Platform(ns)() diff --git a/pkg/platform/defaults.go b/pkg/platform/defaults.go index b385412101..c71571e37d 100644 --- a/pkg/platform/defaults.go +++ b/pkg/platform/defaults.go @@ -53,8 +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 [%s]: setting cluster status", p.Name, p.Namespace) // determine the kind of cluster the platform is installed into isOpenShift, err := openshift.IsOpenShift(c) switch { @@ -68,6 +74,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl } if p.Status.Build.PublishStrategy == "" { + 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 { @@ -76,6 +83,7 @@ func ConfigureDefaults(ctx context.Context, c client.Client, p *v1.IntegrationPl } if p.Status.Build.BuildStrategy == "" { + 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 { @@ -114,6 +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 [%s]: creating build service account", p.Name, p.Namespace) sa := corev1.ServiceAccount{} key := ctrl.ObjectKey{ Name: BuilderServiceAccount, @@ -132,6 +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 [%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" @@ -140,10 +150,12 @@ func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPl if err != nil { return err } + 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 [%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 { @@ -174,23 +186,141 @@ func configureRegistry(ctx context.Context, c client.Client, p *v1.IntegrationPl p.Status.Build.Registry.Address = *address } } + + log.Debugf("Final Registry Address: %s", p.Status.Build.Registry.Address) 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 [%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 [%s]: setting runtime version", p.Name, p.Namespace) p.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion } if p.Status.Build.BaseImage == "" { + 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 [%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 [%s]: setting CLI options", p.Name, p.Namespace) p.Status.Build.Maven.CLIOptions = []string{ "-V", "--no-transfer-progress", @@ -198,6 +328,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error { } } if _, ok := p.Status.Build.PublishStrategyOptions[builder.KanikoPVCName]; !ok { + log.Debugf("Integration Platform %s [%s]: setting publish strategy options", p.Name, p.Namespace) p.Status.Build.PublishStrategyOptions[builder.KanikoPVCName] = p.Name } @@ -208,6 +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 [%s]: setting build timeout", p.Name, p.Namespace) p.Status.Build.Timeout = &metav1.Duration{ Duration: d, } @@ -219,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 { @@ -239,6 +372,7 @@ func setPlatformDefaults(p *v1.IntegrationPlatform, verbose bool) error { } if len(p.Status.Kamelet.Repositories) == 0 { + 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, }) @@ -257,11 +391,14 @@ 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 [%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 [%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 diff --git a/pkg/platform/defaults_test.go b/pkg/platform/defaults_test.go new file mode 100644 index 0000000000..2639420d5c --- /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"]) +}