This is an automated email from the ASF dual-hosted git repository. pcongiusti pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 4eafd3e7a1c4b22cf8750a9c5ede11d5e6c4cce6 Author: Pranjul Kalsi <[email protected]> AuthorDate: Fri Dec 12 14:41:59 2025 +0530 feat: Add default container annotation for kubectl commands --- pkg/trait/cron.go | 3 + pkg/trait/default_container_annotation_test.go | 345 +++++++++++++++++++++++++ pkg/trait/deployment.go | 3 + pkg/trait/knative_service.go | 4 + 4 files changed, 355 insertions(+) diff --git a/pkg/trait/cron.go b/pkg/trait/cron.go index 1969df8d6..ee4c79655 100644 --- a/pkg/trait/cron.go +++ b/pkg/trait/cron.go @@ -240,6 +240,9 @@ func (t *cronTrait) getCronJobFor(e *Environment) *batchv1.CronJob { } } + // Set the default container annotation for kubectl commands + annotations["kubectl.kubernetes.io/default-container"] = defaultContainerName + activeDeadline := defaultCronActiveDeadlineSeconds if t.ActiveDeadlineSeconds != nil { activeDeadline = *t.ActiveDeadlineSeconds diff --git a/pkg/trait/default_container_annotation_test.go b/pkg/trait/default_container_annotation_test.go new file mode 100644 index 000000000..aa6d8a302 --- /dev/null +++ b/pkg/trait/default_container_annotation_test.go @@ -0,0 +1,345 @@ +/* +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 trait + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + serving "knative.dev/serving/pkg/apis/serving/v1" + + 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/internal" + "github.com/apache/camel-k/v2/pkg/util/camel" + "github.com/apache/camel-k/v2/pkg/util/kubernetes" +) + +const ( + defaultContainerAnnotationKey = "kubectl.kubernetes.io/default-container" + testNamespace = "test-ns" + testIntegrationName = "test-integration" +) + +func TestDeploymentHasDefaultContainerAnnotation(t *testing.T) { + deploymentTrait, environment := createNominalDeploymentTest() + err := deploymentTrait.Apply(environment) + require.NoError(t, err) + + deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { + return true + }) + require.NotNil(t, deployment, "Deployment should not be nil") + + podTemplateAnnotations := deployment.Spec.Template.ObjectMeta.Annotations + + t.Logf("Pod Template Annotations: %v", podTemplateAnnotations) + + value, exists := podTemplateAnnotations[defaultContainerAnnotationKey] + + assert.True(t, exists, + "Expected annotation '%s' to be present on pod template, but it was not found. "+ + "Current annotations: %v", defaultContainerAnnotationKey, podTemplateAnnotations) + + if exists { + assert.Equal(t, defaultContainerName, value, + "Expected default container annotation to point to '%s', got '%s'", + defaultContainerName, value) + } +} + +func TestDeploymentDefaultContainerAnnotationWithUserAnnotations(t *testing.T) { + deploymentTrait, environment := createNominalDeploymentTest() + + environment.Integration.Annotations = map[string]string{ + "user-annotation-1": "value1", + "user-annotation-2": "value2", + } + + err := deploymentTrait.Apply(environment) + require.NoError(t, err) + + deployment := environment.Resources.GetDeployment(func(d *appsv1.Deployment) bool { return true }) + require.NotNil(t, deployment) + + annotations := deployment.Spec.Template.ObjectMeta.Annotations + + assert.Equal(t, defaultContainerName, annotations[defaultContainerAnnotationKey], + "Default container annotation should be set to '%s'", defaultContainerName) + + t.Logf("All pod template annotations: %v", annotations) +} + +func TestDeploymentDefaultContainerAnnotationValue(t *testing.T) { + deploymentTrait, environment := createNominalDeploymentTest() + + err := deploymentTrait.Apply(environment) + require.NoError(t, err) + + deployment := environment.Resources.GetDeployment(func(d *appsv1.Deployment) bool { return true }) + require.NotNil(t, deployment) + + value := deployment.Spec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey] + + assert.Equal(t, "integration", value, + "Annotation value should be 'integration' to match the default container name") +} + +func TestKnativeServiceHasDefaultContainerAnnotation(t *testing.T) { + environment := createKnativeServiceTestEnvironment(t, &traitv1.KnativeServiceTrait{ + Trait: traitv1.Trait{ + Enabled: ptr.To(true), + }, + }) + + service := environment.Resources.GetKnativeService(func(s *serving.Service) bool { + return true + }) + require.NotNil(t, service, "Knative Service should not be nil") + + revisionAnnotations := service.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations + + t.Logf("Knative Revision Template Annotations: %v", revisionAnnotations) + + value, exists := revisionAnnotations[defaultContainerAnnotationKey] + + assert.True(t, exists, + "Expected annotation '%s' to be present on Knative revision template, but it was not found. "+ + "Current annotations: %v", defaultContainerAnnotationKey, revisionAnnotations) + + if exists { + assert.Equal(t, defaultContainerName, value, + "Expected default container annotation to point to '%s', got '%s'", + defaultContainerName, value) + } +} + +func TestKnativeServiceDefaultContainerAnnotationWithAutoScaling(t *testing.T) { + minScale := 1 + maxScale := 5 + environment := createKnativeServiceTestEnvironment(t, &traitv1.KnativeServiceTrait{ + Trait: traitv1.Trait{ + Enabled: ptr.To(true), + }, + MinScale: &minScale, + MaxScale: &maxScale, + }) + + service := environment.Resources.GetKnativeService(func(s *serving.Service) bool { return true }) + require.NotNil(t, service) + + annotations := service.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations + + assert.Equal(t, defaultContainerName, annotations[defaultContainerAnnotationKey], + "Default container annotation should be present") + assert.Equal(t, "1", annotations["autoscaling.knative.dev/minScale"], + "Min scale annotation should be present") + assert.Equal(t, "5", annotations["autoscaling.knative.dev/maxScale"], + "Max scale annotation should be present") + + t.Logf("All Knative revision annotations: %v", annotations) +} + +func TestCronJobHasDefaultContainerAnnotation(t *testing.T) { + environment := createCronJobTestEnvironment(t) + + cronJob := environment.Resources.GetCronJob(func(c *batchv1.CronJob) bool { + return true + }) + require.NotNil(t, cronJob, "CronJob should not be nil") + + podTemplateAnnotations := cronJob.Spec.JobTemplate.Spec.Template.ObjectMeta.Annotations + + t.Logf("CronJob Pod Template Annotations: %v", podTemplateAnnotations) + + value, exists := podTemplateAnnotations[defaultContainerAnnotationKey] + + assert.True(t, exists, + "Expected annotation '%s' to be present on CronJob pod template, but it was not found. "+ + "Current annotations: %v", defaultContainerAnnotationKey, podTemplateAnnotations) + + if exists { + assert.Equal(t, defaultContainerName, value, + "Expected default container annotation to point to '%s', got '%s'", + defaultContainerName, value) + } +} + +func TestCronJobDefaultContainerAnnotationWithUserAnnotations(t *testing.T) { + environment := createCronJobTestEnvironmentWithAnnotations(t, map[string]string{ + "custom.annotation/key": "custom-value", + }) + + cronJob := environment.Resources.GetCronJob(func(c *batchv1.CronJob) bool { return true }) + require.NotNil(t, cronJob) + + annotations := cronJob.Spec.JobTemplate.Spec.Template.ObjectMeta.Annotations + + assert.Equal(t, defaultContainerName, annotations[defaultContainerAnnotationKey], + "Default container annotation should be set to '%s'", defaultContainerName) + + t.Logf("All CronJob pod template annotations: %v", annotations) +} + +func TestAllControllerStrategiesHaveDefaultContainerAnnotation(t *testing.T) { + t.Run("Deployment", func(t *testing.T) { + deploymentTrait, environment := createNominalDeploymentTest() + err := deploymentTrait.Apply(environment) + require.NoError(t, err) + + deployment := environment.Resources.GetDeployment(func(d *appsv1.Deployment) bool { return true }) + require.NotNil(t, deployment) + + assert.Equal(t, defaultContainerName, + deployment.Spec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey], + "Deployment should have default-container annotation") + }) + + t.Run("KnativeService", func(t *testing.T) { + environment := createKnativeServiceTestEnvironment(t, &traitv1.KnativeServiceTrait{ + Trait: traitv1.Trait{Enabled: ptr.To(true)}, + }) + + service := environment.Resources.GetKnativeService(func(s *serving.Service) bool { return true }) + require.NotNil(t, service) + + assert.Equal(t, defaultContainerName, + service.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey], + "Knative Service should have default-container annotation") + }) + + t.Run("CronJob", func(t *testing.T) { + environment := createCronJobTestEnvironment(t) + + cronJob := environment.Resources.GetCronJob(func(c *batchv1.CronJob) bool { return true }) + require.NotNil(t, cronJob) + + assert.Equal(t, defaultContainerName, + cronJob.Spec.JobTemplate.Spec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey], + "CronJob should have default-container annotation") + }) +} + +func TestDefaultContainerAnnotationValueIsConsistent(t *testing.T) { + expectedValue := "integration" + + t.Run("Deployment uses correct value", func(t *testing.T) { + deploymentTrait, environment := createNominalDeploymentTest() + _ = deploymentTrait.Apply(environment) + deployment := environment.Resources.GetDeployment(func(d *appsv1.Deployment) bool { return true }) + require.NotNil(t, deployment) + assert.Equal(t, expectedValue, deployment.Spec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey]) + }) + + t.Run("KnativeService uses correct value", func(t *testing.T) { + environment := createKnativeServiceTestEnvironment(t, &traitv1.KnativeServiceTrait{ + Trait: traitv1.Trait{Enabled: ptr.To(true)}, + }) + service := environment.Resources.GetKnativeService(func(s *serving.Service) bool { return true }) + require.NotNil(t, service) + assert.Equal(t, expectedValue, service.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey]) + }) + + t.Run("CronJob uses correct value", func(t *testing.T) { + environment := createCronJobTestEnvironment(t) + cronJob := environment.Resources.GetCronJob(func(c *batchv1.CronJob) bool { return true }) + require.NotNil(t, cronJob) + assert.Equal(t, expectedValue, cronJob.Spec.JobTemplate.Spec.Template.ObjectMeta.Annotations[defaultContainerAnnotationKey]) + }) +} + +func createCronJobTestEnvironment(t *testing.T) *Environment { + t.Helper() + return createCronJobTestEnvironmentWithAnnotations(t, nil) +} + +func createCronJobTestEnvironmentWithAnnotations(t *testing.T, annotations map[string]string) *Environment { + t.Helper() + + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, _ := internal.NewFakeClient() + traitCatalog := NewCatalog(nil) + + environment := &Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: testIntegrationName, + Namespace: testNamespace, + Annotations: annotations, + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseDeploying, + }, + Spec: v1.IntegrationSpec{ + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "routes.java", + Content: `from("cron:tab?schedule=0 0/2 * * ?").to("log:test")`, + }, + Language: v1.LanguageJavaSource, + }, + }, + Traits: v1.Traits{ + Cron: &traitv1.CronTrait{}, + }, + }, + }, + IntegrationKit: &v1.IntegrationKit{ + Status: v1.IntegrationKitStatus{ + Phase: v1.IntegrationKitPhaseReady, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Build: v1.IntegrationPlatformBuildSpec{ + RuntimeVersion: catalog.Runtime.Version, + }, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + c, err := newFakeClient(testNamespace) + require.NoError(t, err) + + tc := NewCatalog(c) + _, _, err = tc.apply(environment) + require.NoError(t, err) + + return environment +} diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go index 5f8cb1817..c10fc4e2d 100644 --- a/pkg/trait/deployment.go +++ b/pkg/trait/deployment.go @@ -118,6 +118,9 @@ func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment { } } + // Set the default container annotation for kubectl commands + annotations["kubectl.kubernetes.io/default-container"] = defaultContainerName + deadline := defaultProgressDeadline if t.ProgressDeadlineSeconds != nil { deadline = *t.ProgressDeadlineSeconds diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go index 2a2676b6e..f0ab59d6f 100644 --- a/pkg/trait/knative_service.go +++ b/pkg/trait/knative_service.go @@ -193,6 +193,10 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, e revisionAnnotations[k] = v } } + + // Set the default container annotation for kubectl + revisionAnnotations["kubectl.kubernetes.io/default-container"] = defaultContainerName + // Set Knative auto-scaling if t.Class != "" { revisionAnnotations[knativeServingClassAnnotation] = t.Class
