Skip to content

Commit

Permalink
feat(cli): promote kameletbinding support
Browse files Browse the repository at this point in the history
  • Loading branch information
squakez committed Jun 17, 2022
1 parent dde508e commit 533f662
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 19 deletions.
28 changes: 24 additions & 4 deletions e2e/common/cli/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestKamelCLIPromote(t *testing.T) {
secData["my-secret-key"] = "very top secret development"
NewPlainTextSecret(nsDev, "my-sec", secData)

t.Run("plain integration", func(t *testing.T) {
t.Run("plain integration dev", func(t *testing.T) {
Expect(Kamel("run", "-n", nsDev, "./files/promote-route.groovy",
"--config", "configmap:my-cm",
"--config", "secret:my-sec",
Expand All @@ -57,13 +57,20 @@ func TestKamelCLIPromote(t *testing.T) {
Eventually(IntegrationLogs(nsDev, "promote-route"), TestTimeoutShort).Should(ContainSubstring("very top secret development"))
})

t.Run("kamelet integration", func(t *testing.T) {
t.Run("kamelet integration dev", func(t *testing.T) {
Expect(CreateTimerKamelet(nsDev, "my-own-timer-source")()).To(Succeed())
Expect(Kamel("run", "-n", nsDev, "files/timer-kamelet-usage.groovy").Execute()).To(Succeed())
Eventually(IntegrationPodPhase(nsDev, "timer-kamelet-usage"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
Eventually(IntegrationLogs(nsDev, "timer-kamelet-usage"), TestTimeoutShort).Should(ContainSubstring("Hello world"))
})

t.Run("kamelet binding dev", func(t *testing.T) {
Expect(CreateTimerKamelet(nsDev, "kb-timer-source")()).To(Succeed())
Expect(Kamel("bind", "kb-timer-source", "log:info", "-p", "message=my-kamelet-binding-rocks", "-n", nsDev).Execute()).To(Succeed())
Eventually(IntegrationPodPhase(nsDev, "kb-timer-source-to-log"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
Eventually(IntegrationLogs(nsDev, "kb-timer-source-to-log"), TestTimeoutShort).Should(ContainSubstring("my-kamelet-binding-rocks"))
})

// Prod environment namespace
WithNewTestNamespace(t, func(nsProd string) {
Expect(Kamel("install", "-n", nsProd).Execute()).To(Succeed())
Expand All @@ -85,7 +92,7 @@ func TestKamelCLIPromote(t *testing.T) {
secData["my-secret-key"] = "very top secret production"
NewPlainTextSecret(nsProd, "my-sec", secData)

t.Run("Production integration", func(t *testing.T) {
t.Run("plain integration promotion", func(t *testing.T) {
Expect(Kamel("promote", "-n", nsDev, "promote-route", "--to", nsProd).Execute()).To(Succeed())
Eventually(IntegrationPodPhase(nsProd, "promote-route"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
Eventually(IntegrationConditionStatus(nsProd, "promote-route", v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue))
Expand All @@ -99,14 +106,27 @@ func TestKamelCLIPromote(t *testing.T) {
Expect(Kamel("promote", "-n", nsDev, "timer-kamelet-usage", "--to", nsProd).Execute()).NotTo(Succeed())
})

t.Run("kamelet integration", func(t *testing.T) {
t.Run("kamelet integration promotion", func(t *testing.T) {
Expect(CreateTimerKamelet(nsProd, "my-own-timer-source")()).To(Succeed())
Expect(Kamel("promote", "-n", nsDev, "timer-kamelet-usage", "--to", nsProd).Execute()).To(Succeed())
Eventually(IntegrationPodPhase(nsProd, "timer-kamelet-usage"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
Eventually(IntegrationLogs(nsProd, "timer-kamelet-usage"), TestTimeoutShort).Should(ContainSubstring("Hello world"))
// They must use the same image
Expect(IntegrationPodImage(nsProd, "timer-kamelet-usage")()).Should(Equal(IntegrationPodImage(nsDev, "timer-kamelet-usage")()))
})

t.Run("no kamelet for kameletbinding in destination", func(t *testing.T) {
Expect(Kamel("promote", "-n", nsDev, "kb-timer-source", "--to", nsProd).Execute()).NotTo(Succeed())
})

t.Run("kamelet binding promotion", func(t *testing.T) {
Expect(CreateTimerKamelet(nsProd, "kb-timer-source")()).To(Succeed())
Expect(Kamel("promote", "-n", nsDev, "kb-timer-source-to-log", "--to", nsProd).Execute()).To(Succeed())
Eventually(IntegrationPodPhase(nsProd, "kb-timer-source-to-log"), TestTimeoutMedium).Should(Equal(corev1.PodRunning))
Eventually(IntegrationLogs(nsProd, "kb-timer-source-to-log"), TestTimeoutShort).Should(ContainSubstring("my-kamelet-binding-rocks"))
// They must use the same image
Expect(IntegrationPodImage(nsProd, "kb-timer-source-to-log")()).Should(Equal(IntegrationPodImage(nsDev, "kb-timer-source-to-log")()))
})
})
})
}
98 changes: 83 additions & 15 deletions pkg/cmd/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/pkg/errors"

v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
"github.com/apache/camel-k/pkg/client"
Expand All @@ -33,6 +34,7 @@ import (
"github.com/apache/camel-k/pkg/util/resource"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -43,8 +45,8 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) (*cobra.Command, *promoteCmdO
}
cmd := cobra.Command{
Use: "promote integration --to [namespace] ...",
Short: "Promote an Integration from an environment to another",
Long: "Promote an Integration from an environment to another, for example from a Development environment to a Production environment",
Short: "Promote an Integration/KameletBinding from an environment to another",
Long: "Promote an Integration/KameletBinding from an environment to another, for example from a Development environment to a Production environment",
PreRunE: decode(&options),
RunE: options.run,
}
Expand All @@ -61,7 +63,7 @@ type promoteCmdOptions struct {

func (o *promoteCmdOptions) validate(_ *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("promote expects an Integration name argument")
return errors.New("promote expects an Integration/KameletBinding name argument")
}
if o.To == "" {
return errors.New("promote expects a destination namespace as --to argument")
Expand All @@ -74,39 +76,61 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args []string) error {
return err
}

it := args[0]
name := args[0]
c, err := o.GetCmdClient()
if err != nil {
return err
return errors.Wrap(err, "could not retrieve cluster client")
}

opSource, err := operatorInfo(o.Context, c, o.Namespace)
if err != nil {
return fmt.Errorf("could not retrieve info for Camel K operator source")
return errors.Wrap(err, "could not retrieve info for Camel K operator source")
}
opDest, err := operatorInfo(o.Context, c, o.To)
if err != nil {
return fmt.Errorf("could not retrieve info for Camel K operator source")
return errors.Wrap(err, "could not retrieve info for Camel K operator destination")
}

err = checkOpsCompatibility(cmd, opSource, opDest)
if err != nil {
return err
return errors.Wrap(err, "could not verify operators compatibility")
}
promoteKameletBinding := false
var sourceIntegration *v1.Integration
// We first look if a KameletBinding with the name exists
sourceKameletBinding, err := o.getKameletBinding(c, name)
if err != nil && !k8serrors.IsNotFound(err) {
return errors.Wrap(err, "problems looking for KameletBinding "+name)
}
if sourceKameletBinding != nil {
promoteKameletBinding = true
}
sourceIntegration, err := o.getIntegration(c, it)
sourceIntegration, err = o.getIntegration(c, name)
if err != nil {
return err
return errors.Wrap(err, "could not get Integration "+name)
}
if sourceIntegration.Status.Phase != v1.IntegrationPhaseRunning {
return fmt.Errorf("could not promote an integration in %s status", sourceIntegration.Status.Phase)
return fmt.Errorf("could not promote an Integration in %s status", sourceIntegration.Status.Phase)
}
err = o.validateDestResources(c, sourceIntegration)
if err != nil {
return err
return errors.Wrap(err, "could not validate destination resources")
}
if promoteKameletBinding {
// KameletBinding promotion
destKameletBinding, err := o.editKameletBinding(sourceKameletBinding, sourceIntegration)
if err != nil {
return errors.Wrap(err, "could not edit KameletBinding "+name)
}

return c.Create(o.Context, destKameletBinding)
}
// Plain Integration promotion
destIntegration, err := o.editIntegration(sourceIntegration)
if err != nil {
return err
if err != nil {
return errors.Wrap(err, "could not edit Integration "+name)
}
}

return c.Create(o.Context, destIntegration)
Expand All @@ -126,14 +150,27 @@ func checkOpsCompatibility(cmd *cobra.Command, source, dest map[string]string) e
return nil
}

func (o *promoteCmdOptions) getKameletBinding(c client.Client, name string) (*v1alpha1.KameletBinding, error) {
it := v1alpha1.NewKameletBinding(o.Namespace, name)
key := k8sclient.ObjectKey{
Name: name,
Namespace: o.Namespace,
}
if err := c.Get(o.Context, key, &it); err != nil {
return nil, err
}

return &it, nil
}

func (o *promoteCmdOptions) getIntegration(c client.Client, name string) (*v1.Integration, error) {
it := v1.NewIntegration(o.Namespace, name)
key := k8sclient.ObjectKey{
Name: name,
Namespace: o.Namespace,
}
if err := c.Get(o.Context, key, &it); err != nil {
return nil, fmt.Errorf("could not find integration %s in namespace %s", it.Name, o.Namespace)
return nil, err
}

return &it, nil
Expand All @@ -145,6 +182,9 @@ func (o *promoteCmdOptions) validateDestResources(c client.Client, it *v1.Integr
var secrets []string
var pvcs []string
var kamelets []string
if it.Spec.Traits == nil {
return nil
}
// Mount trait
mounts := it.Spec.Traits["mount"]
if err := json.Unmarshal(mounts.Configuration.RawMessage, &traits); err != nil {
Expand Down Expand Up @@ -332,6 +372,34 @@ func (o *promoteCmdOptions) editIntegration(it *v1.Integration) (*v1.Integration
return &dst, err
}

func (o *promoteCmdOptions) editKameletBinding(kb *v1alpha1.KameletBinding, it *v1.Integration) (*v1alpha1.KameletBinding, error) {
dst := v1alpha1.NewKameletBinding(o.To, kb.Name)
dst.Spec = *kb.Spec.DeepCopy()
contImage := it.Status.Image
if dst.Spec.Integration == nil {
dst.Spec.Integration = &v1.IntegrationSpec{}
}
if dst.Spec.Integration.Traits == nil {
dst.Spec.Integration.Traits = map[string]v1.TraitSpec{}
}
editedContTrait, err := editContainerImage(dst.Spec.Integration.Traits["container"], contImage)
dst.Spec.Integration.Traits["container"] = editedContTrait
if dst.Spec.Source.Ref != nil {
dst.Spec.Source.Ref.Namespace = o.To
}
if dst.Spec.Sink.Ref != nil {
dst.Spec.Sink.Ref.Namespace = o.To
}
if dst.Spec.Steps != nil {
for _, step := range dst.Spec.Steps {
if step.Ref != nil {
step.Ref.Namespace = o.To
}
}
}
return &dst, err
}

func editContainerImage(contTrait v1.TraitSpec, image string) (v1.TraitSpec, error) {
var editedTrait v1.TraitSpec
m := make(map[string]map[string]interface{})
Expand Down

0 comments on commit 533f662

Please # to comment.