This is an automated email from the ASF dual-hosted git repository.

gfournier pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 82143bcad67af885a1fe98b96e10202b15f25b99
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Dec 14 14:56:16 2024 +0100

    feat(cmd): kamel promote --export-gitops-dir
    
    Introduced a `--export-gitops-dir` flag that will create a simple 
opinionated Kustomize based GitOps overlay directory. The result can be used to 
be stored in Git and be used as a source for any GitOps pipeline.
    
     ```
    kamel promote my-it --to my-dest-namespace --export-gitops-dir 
/path/to/export/dir
    ```
    
    Closes #5456
---
 e2e/common/cli/promote_test.go |  65 ++++++++++
 pkg/cmd/promote.go             | 266 ++++++++++++++++++++++++++++++++++++-
 pkg/cmd/promote_test.go        | 288 ++++++++++++++++++++++++++++++++++++++---
 3 files changed, 603 insertions(+), 16 deletions(-)

diff --git a/e2e/common/cli/promote_test.go b/e2e/common/cli/promote_test.go
new file mode 100644
index 000000000..a8662da78
--- /dev/null
+++ b/e2e/common/cli/promote_test.go
@@ -0,0 +1,65 @@
+//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 (
+       "context"
+       "os"
+       "testing"
+
+       corev1 "k8s.io/api/core/v1"
+
+       . "github.com/onsi/gomega"
+
+       . "github.com/apache/camel-k/v2/e2e/support"
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+)
+
+func TestKamelPromoteGitOps(t *testing.T) {
+       t.Parallel()
+       WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
+               t.Run("build and run gitops", func(t *testing.T) {
+                       g.Expect(KamelRun(t, ctx, ns, 
"files/yaml.yaml").Execute()).To(Succeed())
+                       g.Eventually(IntegrationConditionStatus(t, ctx, ns, 
"yaml", v1.IntegrationConditionReady)).
+                               Should(Equal(corev1.ConditionTrue))
+                       g.Eventually(IntegrationPodPhase(t, ctx, ns, "yaml"), 
TestTimeoutShort).Should(Equal(corev1.PodRunning))
+                       g.Eventually(IntegrationLogs(t, ctx, ns, "yaml"), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+               })
+               WithNewTestNamespace(t, func(ctx context.Context, g *WithT, 
nsTarget string) {
+                       // Export to GitOps directory structure
+                       tmpDir, err := os.MkdirTemp("", "ck-promote-it-*")
+                       if err != nil {
+                               t.Error(err)
+                       }
+                       g.Expect(Kamel(t, ctx, "promote", "yaml", "-n", ns, 
"--to", nsTarget, "--export-gitops-dir", tmpDir).Execute()).To(Succeed())
+                       // Run the exported Integration as it would be any CICD
+                       ExpectExecSucceed(t, g, Kubectl("apply", "-k", 
tmpDir+"/yaml/overlays/"+nsTarget))
+                       g.Eventually(IntegrationPodPhase(t, ctx, nsTarget, 
"yaml"), TestTimeoutShort).Should(Equal(corev1.PodRunning))
+                       g.Eventually(IntegrationConditionStatus(t, ctx, 
nsTarget, "yaml", v1.IntegrationConditionReady), TestTimeoutShort).
+                               Should(Equal(corev1.ConditionTrue))
+                       g.Eventually(IntegrationLogs(t, ctx, nsTarget, "yaml"), 
TestTimeoutShort).Should(ContainSubstring("Magicstring!"))
+                       // Make sure that no IntegrationKit was ever built for 
this Integration
+                       g.Eventually(IntegrationKit(t, ctx, nsTarget, 
"yaml")).Should(Equal(""))
+               })
+       })
+}
diff --git a/pkg/cmd/promote.go b/pkg/cmd/promote.go
index 258569a72..270ce8440 100644
--- a/pkg/cmd/promote.go
+++ b/pkg/cmd/promote.go
@@ -21,12 +21,15 @@ import (
        "context"
        "errors"
        "fmt"
+       "os"
+       "path/filepath"
        "sort"
        "strings"
 
        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/client"
+       "github.com/apache/camel-k/v2/pkg/util/io"
        "github.com/apache/camel-k/v2/pkg/util/kubernetes"
        "github.com/apache/camel-k/v2/pkg/util/sets"
        "github.com/spf13/cobra"
@@ -54,6 +57,7 @@ func newCmdPromote(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *promoteCmdO
        cmd.Flags().StringP("to-operator", "x", "", "The operator id which will 
reconcile the promoted Integration/Pipe")
        cmd.Flags().StringP("output", "o", "", "Output format. One of: 
json|yaml")
        cmd.Flags().BoolP("image", "i", false, "Output the container image 
only")
+       cmd.Flags().String("export-gitops-dir", "", "Export to a Kustomize 
GitOps overlay structure")
 
        return &cmd, &options
 }
@@ -64,6 +68,7 @@ type promoteCmdOptions struct {
        ToOperator   string `mapstructure:"to-operator" yaml:",omitempty"`
        OutputFormat string `mapstructure:"output" yaml:",omitempty"`
        Image        bool   `mapstructure:"image" yaml:",omitempty"`
+       ToGitOpsDir  string `mapstructure:"export-gitops-dir" yaml:",omitempty"`
 }
 
 func (o *promoteCmdOptions) validate(_ *cobra.Command, args []string) error {
@@ -74,7 +79,8 @@ func (o *promoteCmdOptions) validate(_ *cobra.Command, args 
[]string) error {
                return errors.New("promote requires a destination namespace as 
--to argument")
        }
        if o.To == o.Namespace {
-               return errors.New("source and destination namespaces must be 
different in order to avoid promoted Integration/Pipe clashes with the source 
Integration/Pipe")
+               return errors.New("source and destination namespaces must be 
different in order to avoid promoted Integration/Pipe " +
+                       "clashes with the source Integration/Pipe")
        }
        return nil
 }
@@ -142,6 +148,14 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args 
[]string) error {
                if o.OutputFormat != "" {
                        return showPipeOutput(cmd, destPipe, o.OutputFormat, 
c.GetScheme())
                }
+               if o.ToGitOpsDir != "" {
+                       err = appendKustomizePipe(destPipe, o.ToGitOpsDir)
+                       if err != nil {
+                               return err
+                       }
+                       fmt.Fprintln(cmd.OutOrStdout(), `Exported a Kustomize 
based Gitops directory to `+o.ToGitOpsDir+` for "`+name+`" Pipe`)
+                       return nil
+               }
                // Ensure the destination namespace has access to the source 
namespace images
                err = addSystemPullerRoleBinding(o.Context, c, 
sourceIntegration.Namespace, destPipe.Namespace)
                if err != nil {
@@ -164,6 +178,14 @@ func (o *promoteCmdOptions) run(cmd *cobra.Command, args 
[]string) error {
        if o.OutputFormat != "" {
                return showIntegrationOutput(cmd, destIntegration, 
o.OutputFormat)
        }
+       if o.ToGitOpsDir != "" {
+               err = appendKustomizeIntegration(destIntegration, o.ToGitOpsDir)
+               if err != nil {
+                       return err
+               }
+               fmt.Fprintln(cmd.OutOrStdout(), `Exported a Kustomize based 
Gitops directory to `+o.ToGitOpsDir+` for "`+name+`" Integration`)
+               return nil
+       }
        // Ensure the destination namespace has access to the source namespace 
images
        err = addSystemPullerRoleBinding(o.Context, c, 
sourceIntegration.Namespace, destIntegration.Namespace)
        if err != nil {
@@ -421,3 +443,245 @@ func addSystemPullerRoleBinding(ctx context.Context, c 
client.Client, sourceNS s
 func showImageOnly(cmd *cobra.Command, integration *v1.Integration) {
        fmt.Fprintln(cmd.OutOrStdout(), integration.Status.Image)
 }
+
+const kustomizationContent = `apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+`
+
+// appendKustomizeIntegration creates a Kustomize GitOps based directory 
structure for the chosen Integration.
+func appendKustomizeIntegration(dstIt *v1.Integration, destinationDir string) 
error {
+       namespaceDest := dstIt.Namespace
+       if _, err := os.Stat(destinationDir); err != nil {
+               return err
+       }
+
+       baseIt := dstIt.DeepCopy()
+       baseIt.Namespace = ""
+       if baseIt.Annotations != nil {
+               delete(baseIt.Annotations, v1.OperatorIDAnnotation)
+       }
+       appFolderName := strings.ToLower(baseIt.Name)
+
+       newpath := filepath.Join(destinationDir, appFolderName, "routes")
+       err := os.MkdirAll(newpath, io.FilePerm755)
+       if err != nil {
+               return err
+       }
+       for _, src := range baseIt.OriginalSourcesOnly() {
+               srcName := filepath.Join(newpath, src.Name)
+               cnt := []byte(src.Content)
+               if err := os.WriteFile(srcName, cnt, io.FilePerm755); err != 
nil {
+                       return err
+               }
+       }
+
+       newpath = filepath.Join(destinationDir, appFolderName, "base")
+       err = os.MkdirAll(newpath, io.FilePerm755)
+       if err != nil {
+               return err
+       }
+       marshalledIt, err := kubernetes.ToYAML(baseIt)
+       if err != nil {
+               return err
+       }
+       filename := "integration.yaml"
+       itName := filepath.Join(newpath, filename)
+       if err := os.WriteFile(itName, marshalledIt, io.FilePerm755); err != 
nil {
+               return err
+       }
+       baseKustCnt := kustomizationContent + `- ` + filename
+       kustName := filepath.Join(newpath, "kustomization.yaml")
+       if err := os.WriteFile(kustName, []byte(baseKustCnt), io.FilePerm755); 
err != nil {
+               return err
+       }
+
+       newpath = filepath.Join(destinationDir, appFolderName, "overlays", 
namespaceDest)
+       err = os.MkdirAll(newpath, io.FilePerm755)
+       if err != nil {
+               return err
+       }
+       patchName := "patch-integration.yaml"
+       patchedIt := getIntegrationPatch(baseIt)
+       marshalledPatchIt, err := kubernetes.ToYAML(patchedIt)
+       if err != nil {
+               return err
+       }
+       patchFileName := filepath.Join(newpath, patchName)
+       if err := os.WriteFile(patchFileName, marshalledPatchIt, 
io.FilePerm755); err != nil {
+               return err
+       }
+       nsKustCnt := kustomizationContent + `- ../../base`
+       nsKustCnt += `
+namespace: ` + namespaceDest + `
+patches:
+- path: patch-integration.yaml
+`
+       kustName = filepath.Join(newpath, "kustomization.yaml")
+       if err := os.WriteFile(kustName, []byte(nsKustCnt), io.FilePerm755); 
err != nil {
+               return err
+       }
+
+       return err
+}
+
+// getIntegrationPatch will filter those traits/configuration we want to 
include in the Integration patch.
+func getIntegrationPatch(baseIt *v1.Integration) *v1.Integration {
+       patchedTraits := v1.Traits{}
+       baseTraits := baseIt.Spec.Traits
+       if baseTraits.Affinity != nil {
+               patchedTraits.Affinity = baseIt.Spec.Traits.Affinity
+       }
+       if baseTraits.Camel != nil && baseTraits.Camel.Properties != nil {
+               patchedTraits.Camel = &traitv1.CamelTrait{
+                       Properties: baseTraits.Camel.Properties,
+               }
+       }
+       if baseTraits.Container != nil && (baseTraits.Container.RequestCPU != 
"" || baseTraits.Container.RequestMemory != "" ||
+               baseTraits.Container.LimitCPU != "" || 
baseTraits.Container.LimitMemory != "") {
+               patchedTraits.Container = &traitv1.ContainerTrait{
+                       RequestCPU:    baseTraits.Container.RequestCPU,
+                       RequestMemory: baseTraits.Container.RequestMemory,
+                       LimitCPU:      baseTraits.Container.LimitCPU,
+                       LimitMemory:   baseTraits.Container.LimitMemory,
+               }
+       }
+       if baseTraits.Environment != nil && baseTraits.Environment.Vars != nil {
+               patchedTraits.Environment = &traitv1.EnvironmentTrait{
+                       Vars: baseTraits.Environment.Vars,
+               }
+       }
+       if baseTraits.JVM != nil && baseTraits.JVM.Options != nil {
+               patchedTraits.JVM = &traitv1.JVMTrait{
+                       Options: baseTraits.JVM.Options,
+               }
+       }
+       if baseTraits.Mount != nil && (baseTraits.Mount.Configs != nil || 
baseTraits.Mount.Resources != nil ||
+               baseTraits.Mount.Volumes != nil || baseTraits.Mount.EmptyDirs 
!= nil) {
+               patchedTraits.Mount = &traitv1.MountTrait{
+                       Configs:   baseTraits.Mount.Configs,
+                       Resources: baseTraits.Mount.Resources,
+                       Volumes:   baseTraits.Mount.Volumes,
+                       EmptyDirs: baseTraits.Mount.EmptyDirs,
+               }
+       }
+       if baseTraits.Toleration != nil {
+               patchedTraits.Toleration = baseIt.Spec.Traits.Toleration
+       }
+
+       patchedIt := v1.NewIntegration("", baseIt.Name)
+       patchedIt.Spec = v1.IntegrationSpec{
+               Traits: patchedTraits,
+       }
+
+       return &patchedIt
+}
+
+// appendKustomizePipe creates a Kustomize GitOps based directory structure 
for the chosen Pipe.
+func appendKustomizePipe(dstPipe *v1.Pipe, destinationDir string) error {
+       namespaceDest := dstPipe.Namespace
+       if _, err := os.Stat(destinationDir); err != nil {
+               return err
+       }
+
+       basePipe := dstPipe.DeepCopy()
+       basePipe.Namespace = ""
+       if basePipe.Annotations != nil {
+               delete(basePipe.Annotations, v1.OperatorIDAnnotation)
+       }
+       appFolderName := strings.ToLower(basePipe.Name)
+
+       newpath := filepath.Join(destinationDir, appFolderName, "base")
+       err := os.MkdirAll(newpath, io.FilePerm755)
+       if err != nil {
+               return err
+       }
+       marshalledPipe, err := kubernetes.ToYAML(basePipe)
+       if err != nil {
+               return err
+       }
+       filename := "pipe.yaml"
+       itName := filepath.Join(newpath, filename)
+       if err := os.WriteFile(itName, marshalledPipe, io.FilePerm755); err != 
nil {
+               return err
+       }
+       baseKustCnt := kustomizationContent + `- ` + filename
+       kustName := filepath.Join(newpath, "kustomization.yaml")
+       if err := os.WriteFile(kustName, []byte(baseKustCnt), io.FilePerm755); 
err != nil {
+               return err
+       }
+
+       newpath = filepath.Join(destinationDir, appFolderName, "overlays", 
namespaceDest)
+       err = os.MkdirAll(newpath, io.FilePerm755)
+       if err != nil {
+               return err
+       }
+       patchName := "patch-pipe.yaml"
+       patchedPipe := getPipePatch(basePipe)
+       marshalledPatchPipe, err := kubernetes.ToYAML(patchedPipe)
+       if err != nil {
+               return err
+       }
+       patchFileName := filepath.Join(newpath, patchName)
+       if err := os.WriteFile(patchFileName, marshalledPatchPipe, 
io.FilePerm755); err != nil {
+               return err
+       }
+       nsKustCnt := kustomizationContent + `- ../../base`
+       nsKustCnt += `
+namespace: ` + namespaceDest + `
+patches:
+- path: patch-pipe.yaml
+`
+       kustName = filepath.Join(newpath, "kustomization.yaml")
+       if err := os.WriteFile(kustName, []byte(nsKustCnt), io.FilePerm755); 
err != nil {
+               return err
+       }
+
+       return err
+}
+
+// getPipePatch will filter those traits/configuration we want to include in 
the Pipe patch.
+func getPipePatch(basePipe *v1.Pipe) *v1.Pipe {
+       patchedPipe := v1.NewPipe("", basePipe.Name)
+       patchedPipe.Annotations = basePipe.Annotations
+       // Only keep those traits we want to include in the patch
+       for kAnn := range basePipe.Annotations {
+               if strings.HasPrefix(kAnn, v1.TraitAnnotationPrefix) {
+                       if !isPipeTraitPatch(kAnn) {
+                               delete(basePipe.Annotations, kAnn)
+                       }
+               }
+       }
+       return &patchedPipe
+}
+
+// isPipeTraitPatch returns true if it belongs to the list of the opinionated 
traits we want to keep in the patch.
+func isPipeTraitPatch(keyAnnotation string) bool {
+       if strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"affinity") {
+               return true
+       }
+       if keyAnnotation == v1.TraitAnnotationPrefix+"camel.properties" {
+               return true
+       }
+       if strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"container.request") ||
+               strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"container.limit") {
+               return true
+       }
+       if keyAnnotation == v1.TraitAnnotationPrefix+"environment.vars" {
+               return true
+       }
+       if keyAnnotation == v1.TraitAnnotationPrefix+"jvm.options" {
+               return true
+       }
+       if strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"mount.configs") ||
+               strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"mount.resources") ||
+               strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"mount.volumes") ||
+               strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"mount.empty-dirs") {
+               return true
+       }
+       if strings.HasPrefix(keyAnnotation, 
v1.TraitAnnotationPrefix+"toleration") {
+               return true
+       }
+
+       return false
+}
diff --git a/pkg/cmd/promote_test.go b/pkg/cmd/promote_test.go
index 03a942f99..ef163b731 100644
--- a/pkg/cmd/promote_test.go
+++ b/pkg/cmd/promote_test.go
@@ -19,6 +19,8 @@ package cmd
 
 import (
        "fmt"
+       "os"
+       "path/filepath"
        "testing"
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
@@ -142,13 +144,13 @@ func TestPipeDryRun(t *testing.T) {
        dstPlatform.Status.Version = defaults.Version
        dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
        dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
-       defaultKB := nominalPipe("my-kb-test")
-       defaultIntegration, defaultKit := nominalIntegration("my-kb-test")
+       defaultKB := nominalPipe("my-pipe-test")
+       defaultIntegration, defaultKit := nominalIntegration("my-pipe-test")
        srcCatalog := createTestCamelCatalog(srcPlatform)
        dstCatalog := createTestCamelCatalog(dstPlatform)
 
        promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, 
&srcPlatform, &dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, 
&srcCatalog, &dstCatalog)
-       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-pipe-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
        assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat)
        require.NoError(t, err)
        assert.Equal(t, `apiVersion: camel.apache.org/v1
@@ -159,7 +161,7 @@ metadata:
     trait.camel.apache.org/container.image: my-special-image
     trait.camel.apache.org/jvm.classpath: 
/path/to/artifact-1/*:/path/to/artifact-2/*
   creationTimestamp: null
-  name: my-kb-test
+  name: my-pipe-test
   namespace: prod-namespace
 spec:
   sink: {}
@@ -235,7 +237,7 @@ func TestPipeWithMetadataDryRun(t *testing.T) {
        dstPlatform.Status.Version = defaults.Version
        dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
        dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
-       defaultKB := nominalPipe("my-kb-test")
+       defaultKB := nominalPipe("my-pipe-test")
        defaultKB.Annotations = map[string]string{
                "camel.apache.org/operator.id": "camel-k",
                "my-annotation":                "my-value",
@@ -243,12 +245,12 @@ func TestPipeWithMetadataDryRun(t *testing.T) {
        defaultKB.Labels = map[string]string{
                "my-label": "my-value",
        }
-       defaultIntegration, defaultKit := nominalIntegration("my-kb-test")
+       defaultIntegration, defaultKit := nominalIntegration("my-pipe-test")
        srcCatalog := createTestCamelCatalog(srcPlatform)
        dstCatalog := createTestCamelCatalog(dstPlatform)
 
        promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, 
&srcPlatform, &dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, 
&srcCatalog, &dstCatalog)
-       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-pipe-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
        assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat)
        require.NoError(t, err)
        assert.Equal(t, `apiVersion: camel.apache.org/v1
@@ -262,7 +264,7 @@ metadata:
   creationTimestamp: null
   labels:
     my-label: my-value
-  name: my-kb-test
+  name: my-pipe-test
   namespace: prod-namespace
 spec:
   sink: {}
@@ -299,13 +301,13 @@ func TestPipeImageOnly(t *testing.T) {
        dstPlatform.Status.Version = defaults.Version
        dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
        dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
-       defaultKB := nominalPipe("my-kb-test")
-       defaultIntegration, defaultKit := nominalIntegration("my-kb-test")
+       defaultKB := nominalPipe("my-pipe-test")
+       defaultIntegration, defaultKit := nominalIntegration("my-pipe-test")
        srcCatalog := createTestCamelCatalog(srcPlatform)
        dstCatalog := createTestCamelCatalog(dstPlatform)
 
        _, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, 
&dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, &srcCatalog, 
&dstCatalog)
-       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", 
"--to", "prod-namespace", "-i", "-n", "default")
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-pipe-test", 
"--to", "prod-namespace", "-i", "-n", "default")
        require.NoError(t, err)
        assert.Equal(t, "my-special-image\n", output)
 }
@@ -427,7 +429,7 @@ func TestPipeWithSavedTraitsDryRun(t *testing.T) {
        dstPlatform.Status.Version = defaults.Version
        dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
        dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
-       defaultKB := nominalPipe("my-kb-test")
+       defaultKB := nominalPipe("my-pipe-test")
        defaultKB.Annotations = map[string]string{
                "camel.apache.org/operator.id": "camel-k",
                "my-annotation":                "my-value",
@@ -435,12 +437,12 @@ func TestPipeWithSavedTraitsDryRun(t *testing.T) {
        defaultKB.Labels = map[string]string{
                "my-label": "my-value",
        }
-       defaultIntegration, defaultKit := nominalIntegration("my-kb-test")
+       defaultIntegration, defaultKit := nominalIntegration("my-pipe-test")
        srcCatalog := createTestCamelCatalog(srcPlatform)
        dstCatalog := createTestCamelCatalog(dstPlatform)
 
        promoteCmdOptions, promoteCmd, _ := initializePromoteCmdOptions(t, 
&srcPlatform, &dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, 
&srcCatalog, &dstCatalog)
-       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-kb-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-pipe-test", 
"--to", "prod-namespace", "-o", "yaml", "-n", "default")
        assert.Equal(t, "yaml", promoteCmdOptions.OutputFormat)
        require.NoError(t, err)
        assert.Equal(t, `apiVersion: camel.apache.org/v1
@@ -454,7 +456,7 @@ metadata:
   creationTimestamp: null
   labels:
     my-label: my-value
-  name: my-kb-test
+  name: my-pipe-test
   namespace: prod-namespace
 spec:
   sink: {}
@@ -462,3 +464,259 @@ spec:
 status: {}
 `, output)
 }
+
+const expectedGitOpsIt = `apiVersion: camel.apache.org/v1
+kind: Integration
+metadata:
+  creationTimestamp: null
+  name: my-it-test
+spec:
+  traits:
+    affinity:
+      nodeAffinityLabels:
+      - my-node
+    camel:
+      properties:
+      - my.property=val
+      runtimeVersion: 1.2.3
+    container:
+      image: my-special-image
+      imagePullPolicy: Always
+      limitCPU: "1"
+      limitMemory: 1024Mi
+      port: 2000
+      requestCPU: "0.5"
+      requestMemory: 512Mi
+    environment:
+      vars:
+      - MY_VAR=val
+    jvm:
+      classpath: /path/to/artifact-1/*:/path/to/artifact-2/*
+      jar: my.jar
+      options:
+      - -XMX 123
+    mount:
+      configs:
+      - configmap:my-cm
+      - secret:my-sec
+    service:
+      annotations:
+        my-annotation: "123"
+      auto: false
+      enabled: true
+    toleration:
+      taints:
+      - taint1:true
+status: {}
+`
+
+const expectedGitOpsItPatch = `apiVersion: camel.apache.org/v1
+kind: Integration
+metadata:
+  creationTimestamp: null
+  name: my-it-test
+spec:
+  traits:
+    affinity:
+      nodeAffinityLabels:
+      - my-node
+    camel:
+      properties:
+      - my.property=val
+    container:
+      limitCPU: "1"
+      limitMemory: 1024Mi
+      requestCPU: "0.5"
+      requestMemory: 512Mi
+    environment:
+      vars:
+      - MY_VAR=val
+    jvm:
+      options:
+      - -XMX 123
+    mount:
+      configs:
+      - configmap:my-cm
+      - secret:my-sec
+    toleration:
+      taints:
+      - taint1:true
+status: {}
+`
+
+func TestIntegrationGitOps(t *testing.T) {
+       srcPlatform := v1.NewIntegrationPlatform("default", 
platform.DefaultPlatformName)
+       srcPlatform.Status.Version = defaults.Version
+       srcPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+       srcPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
+       dstPlatform := v1.NewIntegrationPlatform("prod-namespace", 
platform.DefaultPlatformName)
+       dstPlatform.Status.Version = defaults.Version
+       dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+       dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
+       defaultIntegration, defaultKit := nominalIntegration("my-it-test")
+       defaultIntegration.Status.Traits = &v1.Traits{
+               Affinity: &trait.AffinityTrait{
+                       NodeAffinityLabels: []string{"my-node"},
+               },
+               Camel: &trait.CamelTrait{
+                       Properties: []string{"my.property=val"},
+               },
+               Container: &trait.ContainerTrait{
+                       LimitCPU:        "1",
+                       LimitMemory:     "1024Mi",
+                       RequestCPU:      "0.5",
+                       RequestMemory:   "512Mi",
+                       Port:            2000,
+                       ImagePullPolicy: corev1.PullAlways,
+               },
+               Environment: &trait.EnvironmentTrait{
+                       Vars: []string{"MY_VAR=val"},
+               },
+               JVM: &trait.JVMTrait{
+                       Jar:     "my.jar",
+                       Options: []string{"-XMX 123"},
+               },
+               Mount: &trait.MountTrait{
+                       Configs: []string{"configmap:my-cm", "secret:my-sec"},
+               },
+               Service: &trait.ServiceTrait{
+                       Trait: trait.Trait{
+                               Enabled: ptr.To(true),
+                       },
+                       Auto: ptr.To(false),
+                       Annotations: map[string]string{
+                               "my-annotation": "123",
+                       },
+               },
+               Toleration: &trait.TolerationTrait{
+                       Taints: []string{"taint1:true"},
+               },
+       }
+       srcCatalog := createTestCamelCatalog(srcPlatform)
+       dstCatalog := createTestCamelCatalog(dstPlatform)
+
+       tmpDir, err := os.MkdirTemp("", "ck-promote-it-*")
+       if err != nil {
+               t.Error(err)
+       }
+
+       _, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, 
&dstPlatform, &defaultIntegration, &defaultKit, &srcCatalog, &dstCatalog)
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-it-test", 
"--to", "prod-namespace", "--export-gitops-dir", tmpDir, "-n", "default")
+       require.NoError(t, err)
+       assert.Contains(t, output, `Exported a Kustomize based Gitops 
directory`)
+
+       baseIt, err := os.ReadFile(filepath.Join(tmpDir, "my-it-test", "base", 
"integration.yaml"))
+       require.NoError(t, err)
+       assert.Equal(t, expectedGitOpsIt, string(baseIt))
+
+       patchIt, err := os.ReadFile(filepath.Join(tmpDir, "my-it-test", 
"overlays", "prod-namespace", "patch-integration.yaml"))
+       require.NoError(t, err)
+       assert.Equal(t, expectedGitOpsItPatch, string(patchIt))
+}
+
+const expectedGitOpsPipe = `apiVersion: camel.apache.org/v1
+kind: Pipe
+metadata:
+  annotations:
+    my-annotation: my-value
+    trait.camel.apache.org/affinity.node-affinity-labels: '[node1,node2]'
+    trait.camel.apache.org/camel.properties: '[a=1]'
+    trait.camel.apache.org/camel.runtime-version: 1.2.3
+    trait.camel.apache.org/container.image: my-special-image
+    trait.camel.apache.org/container.image-pull-policy: Always
+    trait.camel.apache.org/container.limit-cpu: "2"
+    trait.camel.apache.org/container.limit-memory: 1024Mi
+    trait.camel.apache.org/container.request-cpu: "1"
+    trait.camel.apache.org/container.request-memory: 2048Mi
+    trait.camel.apache.org/environment.vars: '[MYVAR=1]'
+    trait.camel.apache.org/jvm.classpath: 
/path/to/artifact-1/*:/path/to/artifact-2/*
+    trait.camel.apache.org/jvm.jar: my.jar
+    trait.camel.apache.org/jvm.options: '[-XMX 123]'
+    trait.camel.apache.org/mount.resources: 
'[configmap:my-cm,secret:my-sec/my-key@/tmp/file.txt]'
+    trait.camel.apache.org/service.auto: "false"
+    trait.camel.apache.org/toleration.taints: '[mytaints:true]'
+  creationTimestamp: null
+  labels:
+    my-label: my-value
+  name: my-pipe-test
+spec:
+  sink: {}
+  source: {}
+status: {}
+`
+
+const expectedGitOpsPipePatch = `apiVersion: camel.apache.org/v1
+kind: Pipe
+metadata:
+  annotations:
+    my-annotation: my-value
+    trait.camel.apache.org/affinity.node-affinity-labels: '[node1,node2]'
+    trait.camel.apache.org/camel.properties: '[a=1]'
+    trait.camel.apache.org/container.limit-cpu: "2"
+    trait.camel.apache.org/container.limit-memory: 1024Mi
+    trait.camel.apache.org/container.request-cpu: "1"
+    trait.camel.apache.org/container.request-memory: 2048Mi
+    trait.camel.apache.org/environment.vars: '[MYVAR=1]'
+    trait.camel.apache.org/jvm.options: '[-XMX 123]'
+    trait.camel.apache.org/mount.resources: 
'[configmap:my-cm,secret:my-sec/my-key@/tmp/file.txt]'
+    trait.camel.apache.org/toleration.taints: '[mytaints:true]'
+  creationTimestamp: null
+  name: my-pipe-test
+spec:
+  sink: {}
+  source: {}
+status: {}
+`
+
+func TestPipeGitOps(t *testing.T) {
+       srcPlatform := v1.NewIntegrationPlatform("default", 
platform.DefaultPlatformName)
+       srcPlatform.Status.Version = defaults.Version
+       srcPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+       srcPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
+       dstPlatform := v1.NewIntegrationPlatform("prod-namespace", 
platform.DefaultPlatformName)
+       dstPlatform.Status.Version = defaults.Version
+       dstPlatform.Status.Build.RuntimeVersion = defaults.DefaultRuntimeVersion
+       dstPlatform.Status.Phase = v1.IntegrationPlatformPhaseReady
+       defaultKB := nominalPipe("my-pipe-test")
+       defaultKB.Annotations = map[string]string{
+               "camel.apache.org/operator.id": "camel-k",
+               "my-annotation":                "my-value",
+               v1.TraitAnnotationPrefix + "affinity.node-affinity-labels": 
"[node1,node2]",
+               v1.TraitAnnotationPrefix + "camel.properties":              
"[a=1]",
+               v1.TraitAnnotationPrefix + "container.limit-cpu":           "2",
+               v1.TraitAnnotationPrefix + "container.limit-memory":        
"1024Mi",
+               v1.TraitAnnotationPrefix + "container.request-cpu":         "1",
+               v1.TraitAnnotationPrefix + "container.request-memory":      
"2048Mi",
+               v1.TraitAnnotationPrefix + "container.image-pull-policy":   
"Always",
+               v1.TraitAnnotationPrefix + "environment.vars":              
"[MYVAR=1]",
+               v1.TraitAnnotationPrefix + "jvm.options":                   
"[-XMX 123]",
+               v1.TraitAnnotationPrefix + "jvm.jar":                       
"my.jar",
+               v1.TraitAnnotationPrefix + "mount.resources":               
"[configmap:my-cm,secret:my-sec/my-key@/tmp/file.txt]",
+               v1.TraitAnnotationPrefix + "service.auto":                  
"false",
+               v1.TraitAnnotationPrefix + "toleration.taints":             
"[mytaints:true]",
+       }
+       defaultKB.Labels = map[string]string{
+               "my-label": "my-value",
+       }
+       defaultIntegration, defaultKit := nominalIntegration("my-pipe-test")
+       srcCatalog := createTestCamelCatalog(srcPlatform)
+       dstCatalog := createTestCamelCatalog(dstPlatform)
+
+       tmpDir, err := os.MkdirTemp("", "ck-promote-pipe-*")
+       if err != nil {
+               t.Error(err)
+       }
+
+       _, promoteCmd, _ := initializePromoteCmdOptions(t, &srcPlatform, 
&dstPlatform, &defaultKB, &defaultIntegration, &defaultKit, &srcCatalog, 
&dstCatalog)
+       output, err := ExecuteCommand(promoteCmd, cmdPromote, "my-pipe-test", 
"--to", "prod-namespace", "--export-gitops-dir", tmpDir, "-n", "default")
+       require.NoError(t, err)
+       assert.Contains(t, output, `Exported a Kustomize based Gitops 
directory`)
+
+       baseIt, err := os.ReadFile(filepath.Join(tmpDir, "my-pipe-test", 
"base", "pipe.yaml"))
+       require.NoError(t, err)
+       assert.Equal(t, expectedGitOpsPipe, string(baseIt))
+
+       patchPipe, err := os.ReadFile(filepath.Join(tmpDir, "my-pipe-test", 
"overlays", "prod-namespace", "patch-pipe.yaml"))
+       require.NoError(t, err)
+       assert.Equal(t, expectedGitOpsPipePatch, string(patchPipe))
+}

Reply via email to