Skip to content

Commit

Permalink
chore: add KeptnApp migration script (keptn#2959)
Browse files Browse the repository at this point in the history
Co-authored-by: Meg McRoberts <[email protected]>
Signed-off-by: vickysomtee <[email protected]>
  • Loading branch information
2 people authored and Vickysomtee committed Apr 22, 2024
1 parent f856b88 commit 8981b5b
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 2 deletions.
16 changes: 16 additions & 0 deletions docs/docs/migrate/keptnapp/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ introduced in the `v1beta1` API version:
3. Add the `app.kubernetes.io/managed-by: keptn` annotation
to the `KeptnApp` resource if it is not already there.

You can migrate your KeptnApp manually or, if you have
[go](https://go.dev/)
installed
on your machine, use the script provided
[here](https://github.com/keptn/lifecycle-toolkit/tree/main/lifecycle-operator/converter).

```bash
go run convert_app.go path_to_keptnapp_to_convert path_to_desired_output_file
```

For instance, to run the example file conversion, the command is:

```bash
go run convert_app.go example_keptnapp.yaml example_output.yaml
```

> **Note**
Be sure that all of your application resources
(such as
Expand Down
6 changes: 4 additions & 2 deletions lifecycle-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ unit-test: manifests fmt vet generate envtest ## Run tests.
go test ./apis/... -v -coverprofile cover-apis.out
go test ./controllers/... -v -coverprofile cover-pkg.out
go test ./webhooks/... -v -coverprofile cover-main.out
go test ./converter -v -coverprofile cover-converter.out
sed -i '/mode: set/d' "cover-cmd.out"
sed -i '/mode: set/d' "cover-apis.out"
sed -i '/mode: set/d' "cover-pkg.out"
sed -i '/mode: set/d' "cover-main.out"
sed -i '/mode: set/d' "cover-converter.out"
echo "mode: set" > cover.out
cat cover-cmd.out cover-main.out cover-pkg.out cover-apis.out >> cover.out
rm cover-cmd.out cover-pkg.out cover-main.out cover-apis.out
cat cover-converter.out cover-cmd.out cover-main.out cover-pkg.out cover-apis.out >> cover.out
rm cover-converter.out cover-cmd.out cover-pkg.out cover-main.out cover-apis.out

.PHONY: component-test
component-test: manifests generate envtest ## Run tests.
Expand Down
112 changes: 112 additions & 0 deletions lifecycle-operator/converter/convert_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"fmt"
"log"
"os"

klcv1alpha3 "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1alpha3"
klcv1beta1 "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)

const keptnAnnotation = "app.kubernetes.io/managed-by"
const keptn = "keptn"

func main() {
if len(os.Args) != 3 {
fmt.Println("Usage: go run main.go app_cr_input_file.yaml output_file.yaml")
os.Exit(1)
}

inputFile := os.Args[1]
outputFile := os.Args[2]

if err := transformKeptnApp(inputFile, outputFile); err != nil {
log.Fatalf("Error transforming KeptnApp: %v", err)
}

fmt.Println("Transformation completed. Output written to", outputFile)
}

func transformKeptnApp(inputFile, outputFile string) error {
inputContent, err := os.ReadFile(inputFile)
if err != nil {
return fmt.Errorf("error reading input file: %w", err)
}

keptnAppV1beta1, keptnAppContext, err := parseAndTransform(inputContent)
if err != nil {
return err
}

outputContent := combineYAML(keptnAppV1beta1, keptnAppContext)
if err := os.WriteFile(outputFile, []byte(outputContent), 0644); err != nil {
return fmt.Errorf("error writing to output file: %w", err)
}
return nil
}

func parseAndTransform(inputContent []byte) ([]byte, []byte, error) {
var keptnApp klcv1alpha3.KeptnApp
if err := yaml.Unmarshal(inputContent, &keptnApp); err != nil {
return nil, nil, fmt.Errorf("error unmarshalling KeptnApp: %w", err)
}

var keptnAppV1beta1 klcv1beta1.KeptnApp
if err := yaml.Unmarshal(inputContent, &keptnAppV1beta1); err != nil {
return nil, nil, fmt.Errorf("error unmarshalling KeptnAppV1beta1: %w", err)
}

addKeptnAnnotation(&keptnAppV1beta1.ObjectMeta)
keptnAppV1beta1.TypeMeta.APIVersion = "lifecycle.keptn.sh/v1beta1"

keptnAppContext := transformKeptnAppContext(keptnApp)

keptnAppV1beta1YAML, err := yaml.Marshal(keptnAppV1beta1)
if err != nil {
return nil, nil, fmt.Errorf("error marshalling KeptnAppV1beta1 to YAML: %w", err)
}

keptnAppContextYAML, err := yaml.Marshal(keptnAppContext)
if err != nil {
return nil, nil, fmt.Errorf("error marshalling KeptnAppContext to YAML: %w", err)
}

return keptnAppV1beta1YAML, keptnAppContextYAML, nil
}

func transformKeptnAppContext(keptnApp klcv1alpha3.KeptnApp) klcv1beta1.KeptnAppContext {
return klcv1beta1.KeptnAppContext{
TypeMeta: metav1.TypeMeta{
Kind: "KeptnAppContext",
APIVersion: "lifecycle.keptn.sh/v1beta1",
},

ObjectMeta: metav1.ObjectMeta{
Name: keptnApp.Name,
Namespace: keptnApp.Namespace,
},
Spec: klcv1beta1.KeptnAppContextSpec{
DeploymentTaskSpec: klcv1beta1.DeploymentTaskSpec{
PreDeploymentTasks: keptnApp.Spec.PreDeploymentTasks,
PreDeploymentEvaluations: keptnApp.Spec.PreDeploymentEvaluations,
PostDeploymentTasks: keptnApp.Spec.PostDeploymentTasks,
PostDeploymentEvaluations: keptnApp.Spec.PostDeploymentEvaluations,
},
},
}
}

func combineYAML(keptnAppV1beta1YAML, keptnAppContextYAML []byte) string {
return fmt.Sprintf("%s\n---\n%s", string(keptnAppV1beta1YAML), string(keptnAppContextYAML))
}

func addKeptnAnnotation(resource *metav1.ObjectMeta) {
annotations := resource.GetAnnotations()
if annotations == nil {
resource.Annotations = make(map[string]string, 1)
}
resource.Annotations[keptnAnnotation] = keptn
}
109 changes: 109 additions & 0 deletions lifecycle-operator/converter/convert_app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"os"
"testing"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const inputFileName = "example_keptnapp.yaml"
const outputFileName = "example_output.yaml"

func TestMigration(t *testing.T) {
// Set up a temporary directory for test files
// Run the main function with test arguments
inputFile := inputFileName
outputFile := outputFileName
os.Args = []string{"main", inputFile, outputFile}
main()

// Read the expected output file content
expectedOutput, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Error reading expected output file: %v", err)
}

// Read the actual output file content
actualOutput, err := os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Error reading actual output file: %v", err)
}

// Assert that the actual output file content matches the expected output
if string(actualOutput) != string(expectedOutput) {
t.Errorf("Unexpected output content. Expected:\n%s\n\nActual:\n%s", string(expectedOutput), string(actualOutput))
}

}

func TestAddKeptnAnnotation(t *testing.T) {
// Test case 1: Annotations map is nil
t.Run("AnnotationsMapIsNil", func(t *testing.T) {
resource := &metav1.ObjectMeta{}
addKeptnAnnotation(resource)

// Check if the annotation was added
if value, exists := resource.Annotations[keptnAnnotation]; !exists || value != keptn {
t.Errorf("Annotation not added correctly. Expected: %s=%s, Actual: %s=%s", keptnAnnotation, keptn, keptnAnnotation, value)
}
})

// Test case 2: Annotations map is not nil
t.Run("AnnotationsMapIsNotNil", func(t *testing.T) {
// Existing annotations
existingAnnotations := map[string]string{
"existing-key": "existing-value",
}

resource := &metav1.ObjectMeta{
Annotations: existingAnnotations,
}

addKeptnAnnotation(resource)

// Check if the annotation was added
if value, exists := resource.Annotations[keptnAnnotation]; !exists || value != keptn {
t.Errorf("Annotation not added correctly. Expected: %s=%s, Actual: %s=%s", keptnAnnotation, keptn, keptnAnnotation, value)
}

// Check if existing annotations are preserved
for key, value := range existingAnnotations {
if resource.Annotations[key] != value {
t.Errorf("Existing annotation %s=%s is not preserved", key, value)
}
}
})
}

func TestParseAndTransformBadYAML(t *testing.T) {
invalidYAML := []byte("This is not valid YAML")
// Attempt to parse and transform the invalid app YAML
_, _, err := parseAndTransform(invalidYAML)
if err == nil {
t.Error("Expected an error but got nil")
}
}

func TestTransformKeptnApp_UnmarshalFailure(t *testing.T) {
tmpDir := t.TempDir()

// Create a temporary file with invalid YAML content
invalidYAML := []byte("This is not valid YAML")
err := os.WriteFile(tmpDir+"/validfile.yaml", invalidYAML, 0644)
require.NoError(t, err)

// Attempt to transform the invalid YAML file
err = transformKeptnApp(tmpDir+"/validfile.yaml", tmpDir+"/output.yaml")

require.Error(t, err)
}

func TestTransformKeptnApp_ReadFileFailure(t *testing.T) {
//unexisting file
tmpDir := t.TempDir()
err := transformKeptnApp(tmpDir+"/validfile.yaml", tmpDir+"/output.yaml")
require.Error(t, err)
t.Log(err)
}
18 changes: 18 additions & 0 deletions lifecycle-operator/converter/example_keptnapp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: lifecycle.keptn.sh/v1alpha3
kind: KeptnApp
metadata:
name: "some-keptn-app"
namespace: "my-app-ns"
spec:
version: "1.2.3"
workloads:
- name: podtato-head-left-arm
version: 0.2.7
preDeploymentTasks:
- pre-deployment-task
preDeploymentEvaluations:
- pre-deployment-evaluation
postDeploymentTasks:
- post-deployment-task
postDeploymentEvaluations:
- post-deployment-evaluation
32 changes: 32 additions & 0 deletions lifecycle-operator/converter/example_output.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apiVersion: lifecycle.keptn.sh/v1beta1
kind: KeptnApp
metadata:
annotations:
app.kubernetes.io/managed-by: keptn
creationTimestamp: null
name: some-keptn-app
namespace: my-app-ns
spec:
version: 1.2.3
workloads:
- name: podtato-head-left-arm
version: 0.2.7
status: {}

---
apiVersion: lifecycle.keptn.sh/v1beta1
kind: KeptnAppContext
metadata:
creationTimestamp: null
name: some-keptn-app
namespace: my-app-ns
spec:
postDeploymentEvaluations:
- post-deployment-evaluation
postDeploymentTasks:
- post-deployment-task
preDeploymentEvaluations:
- pre-deployment-evaluation
preDeploymentTasks:
- pre-deployment-task
status: {}

0 comments on commit 8981b5b

Please sign in to comment.