Skip to content

Commit

Permalink
feat(trait): configuration base trait
Browse files Browse the repository at this point in the history
Moving the logic previously defined in deployment, cron and knative service into a configuration trait

Ref apache#2320
  • Loading branch information
squakez committed Sep 15, 2021
1 parent 1fa61ae commit c0dcda8
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 85 deletions.
60 changes: 34 additions & 26 deletions pkg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,25 +580,26 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd *cobra.Command, c client.C
if err != nil {
return nil, err
}
if err := addIntegrationProperties(props, &integration.Spec); err != nil {
return nil, err
for _, key := range props.Keys() {
kv := fmt.Sprintf("%s=%s", key, props.GetString(key, ""))
if propsTraits, err := convertToTraitParameter(kv, "configuration.properties"); err != nil {
return nil, err
} else {
o.Traits = append(o.Traits, propsTraits...)
}
}

// convert each build configuration to a builder trait property
buildProps, err := mergePropertiesWithPrecedence(o.BuildProperties)
if err != nil {
return nil, err
}
for _, k := range buildProps.Keys() {
v, ok := buildProps.Get(k)
if ok {
entry, err := property.EncodePropertyFileEntry(k, v)
if err != nil {
return nil, err
}
o.Traits = append(o.Traits, fmt.Sprintf("builder.properties=%s", entry))
} else {
for _, key := range buildProps.Keys() {
kv := fmt.Sprintf("%s=%s", key, buildProps.GetString(key, ""))
if buildPropsTraits, err := convertToTraitParameter(kv, "builder.properties"); err != nil {
return nil, err
} else {
o.Traits = append(o.Traits, buildPropsTraits...)
}
}

Expand Down Expand Up @@ -686,6 +687,28 @@ func addResource(resourceLocation string, integrationSpec *v1.IntegrationSpec, e
return nil
}

func convertToTraitParameter(value, traitParameter string) ([]string, error) {
traits := make([]string, 0)
props, err := extractProperties(value)
if err != nil {
return nil, err
}
for _, k := range props.Keys() {
v, ok := props.Get(k)
if ok {
entry, err := property.EncodePropertyFileEntry(k, v)
if err != nil {
return nil, err
}
traits = append(traits, fmt.Sprintf("%s=%s", traitParameter, entry))
} else {
return nil, err
}
}

return traits, nil
}

func (o *runCmdOptions) GetIntegrationName(sources []string) string {
name := ""
if o.IntegrationName != "" {
Expand Down Expand Up @@ -713,21 +736,6 @@ func (o *runCmdOptions) configureTraits(integration *v1.Integration, options []s
return nil
}

func addIntegrationProperties(props *properties.Properties, spec *v1.IntegrationSpec) error {
for _, k := range props.Keys() {
v, _ := props.Get(k)
entry, err := property.EncodePropertyFileEntry(k, v)
if err != nil {
return err
}
spec.AddConfiguration(
"property",
entry,
)
}
return nil
}

func loadPropertyFile(fileName string) (*properties.Properties, error) {
file, err := ioutil.ReadFile(fileName)
if err != nil {
Expand Down
22 changes: 8 additions & 14 deletions pkg/cmd/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,12 @@ func TestAddPropertyFile(t *testing.T) {
assert.Nil(t, tmpFile.Close())
assert.Nil(t, ioutil.WriteFile(tmpFile.Name(), []byte(TestPropertyFileContent), 0644))

spec := v1.IntegrationSpec{}
properties, err := extractProperties("file:" + tmpFile.Name())
properties, err := convertToTraitParameter("file:"+tmpFile.Name(), "trait.properties")
assert.Nil(t, err)
assert.Nil(t, addIntegrationProperties(properties, &spec))
assert.Equal(t, 3, len(spec.Configuration))
assert.Equal(t, `a = b`, spec.Configuration[0].Value)
//assert.Equal(t, `c\=d = e`, spec.Configuration[1].Value)
//assert.Equal(t, `d = c\=e`, spec.Configuration[2].Value)
assert.Equal(t, `f = g:h`, spec.Configuration[1].Value)
assert.Equal(t, `i = j\nk`, spec.Configuration[2].Value)
assert.Equal(t, 3, len(properties))
assert.Equal(t, `trait.properties=a = b`, properties[0])
assert.Equal(t, `trait.properties=f = g:h`, properties[1])
assert.Equal(t, `trait.properties=i = j\nk`, properties[2])
}

func TestRunPropertyFileFlag(t *testing.T) {
Expand All @@ -284,12 +280,10 @@ func TestRunPropertyFileFlag(t *testing.T) {
}

func TestRunProperty(t *testing.T) {
spec := v1.IntegrationSpec{}
properties, err := extractProperties(`key=value\nnewline`)
properties, err := convertToTraitParameter(`key=value\nnewline`, "trait.properties")
assert.Nil(t, err)
assert.Nil(t, addIntegrationProperties(properties, &spec))
assert.Equal(t, 1, len(spec.Configuration))
assert.Equal(t, `key = value\nnewline`, spec.Configuration[0].Value)
assert.Equal(t, 1, len(properties))
assert.Equal(t, `trait.properties=key = value\nnewline`, properties[0])
}

func TestRunResourceFlag(t *testing.T) {
Expand Down
108 changes: 108 additions & 0 deletions pkg/trait/configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
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 (
"fmt"

v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
"github.com/apache/camel-k/pkg/util/property"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
)

// The configuration trait is used to customize the Integration configuration such as properties and resources.
//
// +camel-k:trait=configuration
type configurationTrait struct {
BaseTrait `property:",squash"`
// A list of properties to be provided to the Integration runtime
Properties []string `property:"properties" json:"properties,omitempty"`
}

func newConfigurationTrait() Trait {
return &configurationTrait{
BaseTrait: NewBaseTrait("configuration", 700),
}
}

func (t *configurationTrait) Configure(e *Environment) (bool, error) {
if t.Enabled != nil && !*t.Enabled {
return false, nil
}

return true, nil
}

func (t *configurationTrait) Apply(e *Environment) error {
if e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) ||
e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseRunning) {
// Get all resources
maps := e.computeConfigMaps()
if t.Properties != nil {
// Only user.properties
maps = append(maps, t.computeUserProperties(e)...)
}
e.Resources.AddAll(maps)
}

return nil
}

func (t *configurationTrait) IsPlatformTrait() bool {
return true
}

func (t *configurationTrait) computeUserProperties(e *Environment) []ctrl.Object {
maps := make([]ctrl.Object, 0)

// combine properties of integration with kit, integration
// properties have the priority
userProperties := ""

for _, prop := range t.Properties {
k, v := property.SplitPropertyFileEntry(prop)
userProperties += fmt.Sprintf("%s=%s\n", k, v)
}

if userProperties != "" {
maps = append(
maps,
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: e.Integration.Name + "-user-properties",
Namespace: e.Integration.Namespace,
Labels: map[string]string{
v1.IntegrationLabel: e.Integration.Name,
"camel.apache.org/properties.type": "user",
},
},
Data: map[string]string{
"application.properties": userProperties,
},
},
)
}

return maps
}
98 changes: 98 additions & 0 deletions pkg/trait/configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
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"

v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
"github.com/apache/camel-k/pkg/util/kubernetes"
"github.com/apache/camel-k/pkg/util/test"

"github.com/stretchr/testify/assert"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestApplyConfigurationTraitWithProperties(t *testing.T) {
configurationTrait, environment := createNominalConfigurationTest()
configurationTrait.Properties = []string{"a=b", "c=d"}
err := configurationTrait.Apply(environment)
assert.Nil(t, err)

userPropertiesCm := environment.Resources.GetConfigMap(func(cm *corev1.ConfigMap) bool {
return cm.Labels["camel.apache.org/properties.type"] == "user"
})
assert.NotNil(t, userPropertiesCm)
assert.Equal(t, map[string]string{
"application.properties": "a=b\nc=d\n",
}, userPropertiesCm.Data)
}

func createNominalConfigurationTest() (*configurationTrait, *Environment) {
trait := newConfigurationTrait().(*configurationTrait)
trait.Enabled = BoolP(true)
trait.Client, _ = test.NewFakeClient(&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "integration-name",
Namespace: "namespace",
Labels: map[string]string{
v1.IntegrationLabel: "integration-name",
},
},
})

replicas := int32(3)

environment := &Environment{
Catalog: NewCatalog(nil),
Integration: &v1.Integration{
ObjectMeta: metav1.ObjectMeta{
Name: "integration-name",
},
Spec: v1.IntegrationSpec{
Replicas: &replicas,
Traits: make(map[string]v1.TraitSpec),
},
Status: v1.IntegrationStatus{
Phase: v1.IntegrationPhaseDeploying,
},
},
IntegrationKit: &v1.IntegrationKit{
Status: v1.IntegrationKitStatus{
Phase: v1.IntegrationKitPhaseReady,
},
},
Platform: &v1.IntegrationPlatform{
ObjectMeta: metav1.ObjectMeta{
Namespace: "namespace",
},
Spec: v1.IntegrationPlatformSpec{
Cluster: v1.IntegrationPlatformClusterOpenShift,
Profile: v1.TraitProfileKnative,
},
},
Resources: kubernetes.NewCollection(),
ApplicationProperties: make(map[string]string),
}
environment.Platform.ResyncStatusFullConfig()

return trait, environment
}
3 changes: 0 additions & 3 deletions pkg/trait/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,6 @@ func (t *cronTrait) Apply(e *Environment) error {
e.Interceptors = append(e.Interceptors, "cron")

cronJob := t.getCronJobFor(e)
maps := e.computeConfigMaps()

e.Resources.AddAll(maps)
e.Resources.Add(cronJob)

e.Integration.Status.SetCondition(
Expand Down
3 changes: 0 additions & 3 deletions pkg/trait/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ func (t *deploymentTrait) ControllerStrategySelectorOrder() int {
func (t *deploymentTrait) Apply(e *Environment) error {
if e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseDeploying) ||
e.InPhase(v1.IntegrationKitPhaseReady, v1.IntegrationPhaseRunning) {
maps := e.computeConfigMaps()
deployment := t.getDeploymentFor(e)

e.Resources.AddAll(maps)
e.Resources.Add(deployment)

e.Integration.Status.SetCondition(
Expand Down
14 changes: 1 addition & 13 deletions pkg/trait/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,9 @@ func TestApplyDeploymentTraitWhileResolvingKitDoesNotSucceed(t *testing.T) {

func TestApplyDeploymentTraitWhileDeployingIntegrationDoesSucceed(t *testing.T) {
deploymentTrait, environment := createNominalDeploymentTest()
environment.Integration.Spec.Configuration = append(environment.Integration.Spec.Configuration, v1.ConfigurationSpec{
Type: "property",
Value: "a=b",
})

err := deploymentTrait.Apply(environment)

assert.Nil(t, err)

assert.NotNil(t, environment.Resources.GetConfigMap(func(cm *corev1.ConfigMap) bool {
return cm.Labels["camel.apache.org/properties.type"] == "user"
}))
assert.Nil(t, environment.Resources.GetConfigMap(func(cm *corev1.ConfigMap) bool {
return cm.Labels["camel.apache.org/properties.type"] == "application"
}))

deployment := environment.Resources.GetDeployment(func(deployment *appsv1.Deployment) bool { return true })
assert.NotNil(t, deployment)
assert.Equal(t, "integration-name", deployment.Name)
Expand Down Expand Up @@ -182,6 +169,7 @@ func createNominalDeploymentTest() (*deploymentTrait, *Environment) {
},
Spec: v1.IntegrationSpec{
Replicas: &replicas,
Traits: make(map[string]v1.TraitSpec),
},
Status: v1.IntegrationStatus{
Phase: v1.IntegrationPhaseDeploying,
Expand Down
3 changes: 0 additions & 3 deletions pkg/trait/knative_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,6 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) {

func (t *knativeServiceTrait) Apply(e *Environment) error {
ksvc := t.getServiceFor(e)
maps := e.computeConfigMaps()

e.Resources.AddAll(maps)
e.Resources.Add(ksvc)

e.Integration.Status.SetCondition(
Expand Down
Loading

0 comments on commit c0dcda8

Please sign in to comment.