diff --git a/cmd/katenary/main.go b/cmd/katenary/main.go index 4f2baf6..3f84d90 100644 --- a/cmd/katenary/main.go +++ b/cmd/katenary/main.go @@ -22,7 +22,11 @@ Each [command] and subcommand has got an "help" and "--help" flag to show more i ` func main() { - // The base command + rootCmd := buildRootCmd() + rootCmd.Execute() +} + +func buildRootCmd() *cobra.Command { rootCmd := &cobra.Command{ Use: "katenary", Long: longHelp, @@ -42,7 +46,7 @@ func main() { generateLabelHelpCommand(), ) - rootCmd.Execute() + return rootCmd } const completionHelp = `To load completions: diff --git a/cmd/katenary/main_test.go b/cmd/katenary/main_test.go new file mode 100644 index 0000000..1b0f947 --- /dev/null +++ b/cmd/katenary/main_test.go @@ -0,0 +1,17 @@ +package main + +import "testing" + +func TestBuildCommand(t *testing.T) { + rootCmd := buildRootCmd() + if rootCmd == nil { + t.Errorf("Expected rootCmd to be defined") + } + if rootCmd.Use != "katenary" { + t.Errorf("Expected rootCmd.Use to be katenary, got %s", rootCmd.Use) + } + numCommands := 5 + if len(rootCmd.Commands()) != numCommands { + t.Errorf("Expected %d command, got %d", numCommands, len(rootCmd.Commands())) + } +} diff --git a/generator/cronJob.go b/generator/cronJob.go index c0f6f3d..5020c3a 100644 --- a/generator/cronJob.go +++ b/generator/cronJob.go @@ -4,6 +4,7 @@ import ( "log" "strings" + labelstructs "katenary/generator/labelStructs" "katenary/utils" "github.com/compose-spec/compose-go/types" @@ -31,17 +32,18 @@ func NewCronJob(service types.ServiceConfig, chart *HelmChart, appName string) ( if !ok { return nil, nil } - mapping := struct { - Image string `yaml:"image,omitempty"` - Command string `yaml:"command"` - Schedule string `yaml:"schedule"` - Rbac bool `yaml:"rbac"` - }{ - Image: "", - Command: "", - Schedule: "", - Rbac: false, - } + //mapping := struct { + // Image string `yaml:"image,omitempty"` + // Command string `yaml:"command"` + // Schedule string `yaml:"schedule"` + // Rbac bool `yaml:"rbac"` + //}{ + // Image: "", + // Command: "", + // Schedule: "", + // Rbac: false, + //} + var mapping labelstructs.CronJob if err := goyaml.Unmarshal([]byte(labels), &mapping); err != nil { log.Fatalf("Error parsing cronjob labels: %s", err) return nil, nil diff --git a/generator/cronJob_test.go b/generator/cronJob_test.go new file mode 100644 index 0000000..cb7ee0c --- /dev/null +++ b/generator/cronJob_test.go @@ -0,0 +1,115 @@ +package generator + +import ( + "os" + "strings" + "testing" + + v1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +func TestBasicCronJob(t *testing.T) { + compose_file := ` +services: + cron: + image: fedora + labels: + katenary.v3/cronjob: | + image: alpine + command: echo hello + schedule: "*/1 * * * *" + rbac: false +` + tmpDir := setup(compose_file) + defer teardown(tmpDir) + + currentDir, _ := os.Getwd() + os.Chdir(tmpDir) + defer os.Chdir(currentDir) + + output := _compile_test(t, "-s", "templates/cron/cronjob.yaml") + cronJob := batchv1.CronJob{} + if err := yaml.Unmarshal([]byte(output), &cronJob); err != nil { + t.Errorf(unmarshalError, err) + } + if cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image != "alpine:latest" { + t.Errorf("Expected image to be alpine, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Image) + } + combinedCommand := strings.Join(cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command, " ") + if combinedCommand != "sh -c echo hello" { + t.Errorf("Expected command to be sh -c echo hello, got %s", combinedCommand) + } + if cronJob.Spec.Schedule != "*/1 * * * *" { + t.Errorf("Expected schedule to be */1 * * * *, got %s", cronJob.Spec.Schedule) + } + + // ensure that there are a deployment for the fedora Container + var err error + output, err = helmTemplate(ConvertOptions{ + OutputDir: "./chart", + }, "-s", "templates/cron/deployment.yaml") + if err != nil { + t.Errorf("Error: %s", err) + } + deployment := v1.Deployment{} + if err := yaml.Unmarshal([]byte(output), &deployment); err != nil { + t.Errorf(unmarshalError, err) + } + if deployment.Spec.Template.Spec.Containers[0].Image != "fedora:latest" { + t.Errorf("Expected image to be fedora, got %s", deployment.Spec.Template.Spec.Containers[0].Image) + } +} + +func TestCronJobbWithRBAC(t *testing.T) { + compose_file := ` +services: + cron: + image: fedora + labels: + katenary.v3/cronjob: | + image: alpine + command: echo hello + schedule: "*/1 * * * *" + rbac: true +` + + tmpDir := setup(compose_file) + defer teardown(tmpDir) + + currentDir, _ := os.Getwd() + os.Chdir(tmpDir) + defer os.Chdir(currentDir) + + output := _compile_test(t, "-s", "templates/cron/cronjob.yaml") + cronJob := batchv1.CronJob{} + if err := yaml.Unmarshal([]byte(output), &cronJob); err != nil { + t.Errorf(unmarshalError, err) + } + if cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName == "" { + t.Errorf("Expected ServiceAccountName to be set") + } + + // find the service account file + output, err := helmTemplate(ConvertOptions{ + OutputDir: "./chart", + }, "-s", "templates/cron/serviceaccount.yaml") + if err != nil { + t.Errorf("Error: %s", err) + } + serviceAccount := corev1.ServiceAccount{} + + if err := yaml.Unmarshal([]byte(output), &serviceAccount); err != nil { + t.Errorf(unmarshalError, err) + } + if serviceAccount.Name == "" { + t.Errorf("Expected ServiceAccountName to be set") + } + + // ensure that the serviceAccount is equal to the cronJob + if serviceAccount.Name != cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName { + t.Errorf("Expected ServiceAccountName to be %s, got %s", cronJob.Spec.JobTemplate.Spec.Template.Spec.ServiceAccountName, serviceAccount.Name) + } +} diff --git a/generator/deployment_test.go b/generator/deployment_test.go index f9df258..3dddb0a 100644 --- a/generator/deployment_test.go +++ b/generator/deployment_test.go @@ -5,16 +5,17 @@ import ( "testing" v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" ) func TestGenerate(t *testing.T) { - _compose_file := ` + compose_file := ` services: web: image: nginx:1.29 ` - tmpDir := setup(_compose_file) + tmpDir := setup(compose_file) defer teardown(tmpDir) currentDir, _ := os.Getwd() @@ -38,3 +39,99 @@ services: t.Errorf("Expected image to be nginx:1.29, got %s", dt.Spec.Template.Spec.Containers[0].Image) } } + +func TestGenerateOneDeploymentWithSamePod(t *testing.T) { + compose_file := ` +services: + web: + image: nginx:1.29 + ports: + - 80:80 + + fpm: + image: php:fpm + ports: + - 9000:9000 + labels: + katenary.v3/same-pod: web +` + + tmpDir := setup(compose_file) + defer teardown(tmpDir) + + currentDir, _ := os.Getwd() + os.Chdir(tmpDir) + defer os.Chdir(currentDir) + + output := _compile_test(t, "-s", "templates/web/deployment.yaml") + dt := v1.Deployment{} + if err := yaml.Unmarshal([]byte(output), &dt); err != nil { + t.Errorf(unmarshalError, err) + } + + if len(dt.Spec.Template.Spec.Containers) != 2 { + t.Errorf("Expected 2 containers, got %d", len(dt.Spec.Template.Spec.Containers)) + } + // endsure that the fpm service is not created + + var err error + output, err = helmTemplate(ConvertOptions{ + OutputDir: "./chart", + }, "-s", "templates/fpm/deployment.yaml") + if err == nil { + t.Errorf("Expected error, got nil") + } + + // ensure that the web service is created and has got 2 ports + output, err = helmTemplate(ConvertOptions{ + OutputDir: "./chart", + }, "-s", "templates/web/service.yaml") + if err != nil { + t.Errorf("Error: %s", err) + } + service := corev1.Service{} + if err := yaml.Unmarshal([]byte(output), &service); err != nil { + t.Errorf(unmarshalError, err) + } + + if len(service.Spec.Ports) != 2 { + t.Errorf("Expected 2 ports, got %d", len(service.Spec.Ports)) + } +} + +func TestDependsOn(t *testing.T) { + compose_file := ` +services: + web: + image: nginx:1.29 + ports: + - 80:80 + depends_on: + - database + + database: + image: mariadb:10.5 + ports: + - 3306:3306 +` + tmpDir := setup(compose_file) + defer teardown(tmpDir) + + currentDir, _ := os.Getwd() + os.Chdir(tmpDir) + defer os.Chdir(currentDir) + + output := _compile_test(t, "-s", "templates/web/deployment.yaml") + dt := v1.Deployment{} + if err := yaml.Unmarshal([]byte(output), &dt); err != nil { + t.Errorf(unmarshalError, err) + } + + if len(dt.Spec.Template.Spec.Containers) != 1 { + t.Errorf("Expected 1 container, got %d", len(dt.Spec.Template.Spec.Containers)) + } + // find an init container + if len(dt.Spec.Template.Spec.InitContainers) != 1 { + t.Errorf("Expected 1 init container, got %d", len(dt.Spec.Template.Spec.InitContainers)) + } +} diff --git a/generator/katenaryLabels_test.go b/generator/katenaryLabels_test.go new file mode 100644 index 0000000..4cfbcc3 --- /dev/null +++ b/generator/katenaryLabels_test.go @@ -0,0 +1,76 @@ +package generator + +import ( + _ "embed" + "reflect" + "testing" +) + +var testingKatenaryPrefix = Prefix() + +func TestPrefix(t *testing.T) { + tests := []struct { + name string + want string + }{ + { + name: "TestPrefix", + want: "katenary.v3", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Prefix(); got != tt.want { + t.Errorf("Prefix() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_labelName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want Label + }{ + { + name: "Test_labelName", + args: args{ + name: "main-app", + }, + want: testingKatenaryPrefix + "/main-app", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := labelName(tt.args.name); !reflect.DeepEqual(got, tt.want) { + t.Errorf("labelName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetLabelHelp(t *testing.T) { + help := GetLabelHelp(false) + if help == "" { + t.Errorf("GetLabelHelp() = %v, want %v", help, "Help") + } + help = GetLabelHelp(true) + if help == "" { + t.Errorf("GetLabelHelp() = %v, want %v", help, "Help") + } +} + +func TestGetLabelHelpFor(t *testing.T) { + help := GetLabelHelpFor("main-app", false) + if help == "" { + t.Errorf("GetLabelHelpFor() = %v, want %v", help, "Help") + } + help = GetLabelHelpFor("main-app", true) + if help == "" { + t.Errorf("GetLabelHelpFor() = %v, want %v", help, "Help") + } +} diff --git a/generator/labelStructs/configMap.go b/generator/labelStructs/configMap.go new file mode 100644 index 0000000..5457618 --- /dev/null +++ b/generator/labelStructs/configMap.go @@ -0,0 +1,8 @@ +package labelstructs + +type CronJob struct { + Image string `yaml:"image,omitempty"` + Command string `yaml:"command"` + Schedule string `yaml:"schedule"` + Rbac bool `yaml:"rbac"` +} diff --git a/generator/volume_test.go b/generator/volume_test.go index 9d327d3..d2f49b9 100644 --- a/generator/volume_test.go +++ b/generator/volume_test.go @@ -11,7 +11,7 @@ import ( ) func TestGenerateWithBoundVolume(t *testing.T) { - _compose_file := ` + compose_file := ` services: web: image: nginx:1.29 @@ -20,7 +20,7 @@ services: volumes: data: ` - tmpDir := setup(_compose_file) + tmpDir := setup(compose_file) defer teardown(tmpDir) currentDir, _ := os.Getwd() @@ -40,7 +40,7 @@ volumes: } func TestWithStaticFiles(t *testing.T) { - _compose_file := ` + compose_file := ` services: web: image: nginx:1.29 @@ -50,8 +50,8 @@ services: %s/configmap-files: |- - ./static ` - _compose_file = fmt.Sprintf(_compose_file, katenaryLabelPrefix) - tmpDir := setup(_compose_file) + compose_file = fmt.Sprintf(compose_file, katenaryLabelPrefix) + tmpDir := setup(compose_file) defer teardown(tmpDir) // create a static directory with an index.html file @@ -98,3 +98,93 @@ services: t.Errorf("Expected index.html to be