From ac22a86693e8f3ca21959d55a2ce36d1f17c2fd3 Mon Sep 17 00:00:00 2001 From: Zorian Motso Date: Thu, 12 Sep 2024 16:42:29 +0300 Subject: [PATCH] feat: Implement fallback strategy for RPM packaging (#123) - Add default template files for RPM if a language is not supported. - Update operator to create Makefile.kuberocketci if a Makefile already exists. --- .../rpm-package/default/service.tmpl | 0 .../rpm-package/default/spec.tmpl | 0 .../codebase/service/template/template.go | 2 +- pkg/util/template.go | 74 ++++---- pkg/util/template_test.go | 167 ++++++++++++++++-- 5 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 build/templates/applications/rpm-package/default/service.tmpl create mode 100644 build/templates/applications/rpm-package/default/spec.tmpl diff --git a/build/templates/applications/rpm-package/default/service.tmpl b/build/templates/applications/rpm-package/default/service.tmpl new file mode 100644 index 00000000..e69de29b diff --git a/build/templates/applications/rpm-package/default/spec.tmpl b/build/templates/applications/rpm-package/default/spec.tmpl new file mode 100644 index 00000000..e69de29b diff --git a/controllers/codebase/service/template/template.go b/controllers/codebase/service/template/template.go index 295f98a1..31c78311 100644 --- a/controllers/codebase/service/template/template.go +++ b/controllers/codebase/service/template/template.go @@ -37,7 +37,7 @@ func PrepareTemplates(ctx context.Context, c client.Client, cb *codebaseApi.Code return fmt.Errorf("failed to get assets dir: %w", err) } - if err := util.CopyTemplate(cb.Spec.DeploymentScript, workDir, assetsDir, cf); err != nil { + if err := util.CopyTemplate(ctx, cb.Spec.DeploymentScript, workDir, assetsDir, cf); err != nil { return fmt.Errorf("failed to copy template for %v codebase: %w", cb.Name, err) } diff --git a/pkg/util/template.go b/pkg/util/template.go index 8a654e27..7adc382e 100644 --- a/pkg/util/template.go +++ b/pkg/util/template.go @@ -1,12 +1,15 @@ package util import ( + "context" "errors" "fmt" "os" "path" "text/template" + ctrl "sigs.k8s.io/controller-runtime" + "github.com/epam/edp-codebase-operator/v2/pkg/model" ) @@ -159,78 +162,87 @@ func CopyHelmChartTemplates(deploymentScript, templatesDest, assetsDir string, c return nil } -func CopyRpmPackageTemplates(templatesDest, assetsDir string, config *model.ConfigGoTemplating) error { - log.Info("start handling RPM Package templates", logCodebaseNameKey, config.Name) +func CopyRpmPackageTemplates(ctx context.Context, templatesDest, assetsDir string, config *model.ConfigGoTemplating) error { + l := ctrl.LoggerFrom(ctx) + l.Info("Start handling RPM Package templates") // Define template paths makefileTemplatePath := path.Join(assetsDir, "templates/applications/rpm-package/Makefile.tmpl") rpmlintTemplatePath := path.Join(assetsDir, "templates/applications/rpm-package/.rpmlintrc.toml") + specTemplatePath := path.Join(assetsDir, fmt.Sprintf("templates/applications/rpm-package/%s/spec.tmpl", config.Lang)) serviceTemplatePath := path.Join(assetsDir, fmt.Sprintf("templates/applications/rpm-package/%s/service.tmpl", config.Lang)) + + if _, err := os.Stat(specTemplatePath); os.IsNotExist(err) { + specTemplatePath = path.Join(assetsDir, "templates/applications/rpm-package/default/spec.tmpl") + serviceTemplatePath = path.Join(assetsDir, "templates/applications/rpm-package/default/service.tmpl") + } else if err != nil { + return fmt.Errorf("failed to check if %q exists: %w", specTemplatePath, err) + } + // Define destination paths makefileDestPath := path.Join(templatesDest, "Makefile") + if _, err := os.Stat(makefileDestPath); err == nil { + makefileDestPath = path.Join(templatesDest, "Makefile.kuberocketci") + } + specDestPath := path.Join(templatesDest, fmt.Sprintf("%s.spec", config.Name)) serviceDestPath := path.Join(templatesDest, fmt.Sprintf("%s.service", config.Name)) rpmlintDestPath := path.Join(templatesDest, ".rpmlintrc.toml") - // Create destination files - makefileDestFile, err := os.Create(makefileDestPath) - if err != nil { - return fmt.Errorf("failed to create destination file %q: %w", makefileDestPath, err) - } - - specDestFile, err := os.Create(specDestPath) - if err != nil { - return fmt.Errorf("failed to create destination file %q: %w", specDestPath, err) - } - - serviceDestFile, err := os.Create(serviceDestPath) - if err != nil { - return fmt.Errorf("failed to create destination file %q: %w", serviceDestPath, err) + // Create and render templates + if err := createAndRenderTemplate(makefileDestPath, makefileTemplatePath, "Makefile.tmpl", config); err != nil { + return err } - // Render and copy templates - err = renderTemplate(makefileDestFile, makefileTemplatePath, "Makefile.tmpl", config) - if err != nil { + if err := createAndRenderTemplate(specDestPath, specTemplatePath, "spec.tmpl", config); err != nil { return err } - err = renderTemplate(specDestFile, specTemplatePath, "spec.tmpl", config) - if err != nil { + if err := createAndRenderTemplate(serviceDestPath, serviceTemplatePath, "service.tmpl", config); err != nil { return err } - err = renderTemplate(serviceDestFile, serviceTemplatePath, "service.tmpl", config) - if err != nil { + if err := CopyFile(rpmlintTemplatePath, rpmlintDestPath); err != nil { return err } - err = CopyFile(rpmlintTemplatePath, rpmlintDestPath) + l.Info("RPM Package templates have been copied and rendered") + + return nil +} + +func createAndRenderTemplate(destPath, templatePath, templateName string, config *model.ConfigGoTemplating) error { + destFile, err := os.Create(destPath) if err != nil { - return err + return fmt.Errorf("failed to create destination file %q: %w", destPath, err) } - log.Info("RPM Package templates have been copied and rendered", logCodebaseNameKey, config.Name) + defer func() { + if cerr := destFile.Close(); cerr != nil { + log.Error(cerr, "failed to close destination file", "file", destPath) + } + }() - return nil + return renderTemplate(destFile, templatePath, templateName, config) } -func CopyTemplate(deploymentScript, workDir, assetsDir string, cf *model.ConfigGoTemplating) error { +func CopyTemplate(ctx context.Context, deploymentScript, workDir, assetsDir string, cf *model.ConfigGoTemplating) error { switch deploymentScript { case HelmChartDeploymentScriptType: templatesDest := path.Join(workDir, "deploy-templates") if DoesDirectoryExist(templatesDest) { - log.Info("deploy-templates folder already exists") + ctrl.LoggerFrom(ctx).Info("Deploy-templates folder already exists") return nil } return CopyHelmChartTemplates(deploymentScript, templatesDest, assetsDir, cf) case RpmPackageDeploymentScriptType: - return CopyRpmPackageTemplates(workDir, assetsDir, cf) + return CopyRpmPackageTemplates(ctx, workDir, assetsDir, cf) default: - return errors.New("Unsupported deployment type") + return errors.New("unsupported deployment type") } } diff --git a/pkg/util/template_test.go b/pkg/util/template_test.go index 7191f698..ddc5df00 100644 --- a/pkg/util/template_test.go +++ b/pkg/util/template_test.go @@ -1,12 +1,15 @@ package util import ( + "context" "fmt" "os" "testing" + "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + ctrl "sigs.k8s.io/controller-runtime" "github.com/epam/edp-codebase-operator/v2/pkg/model" ) @@ -23,7 +26,13 @@ func TestCopyTemplate_HelmTemplates_ShouldPass(t *testing.T) { GitURL: "https://example.com", } - err := CopyTemplate(HelmChartDeploymentScriptType, testDir, "../../build", cf) + err := CopyTemplate( + ctrl.LoggerInto(context.Background(), logr.Discard()), + HelmChartDeploymentScriptType, + testDir, + "../../build", + cf, + ) require.NoError(t, err) chf := fmt.Sprintf("%v/deploy-templates/Chart.yaml", testDir) @@ -42,16 +51,39 @@ func TestCopyTemplate_HelmTemplates_ShouldPass(t *testing.T) { assert.Contains(t, string(b), "home: https://example.com") } +func TestCopyTemplate_HelmTemplates_DirectoryExists(t *testing.T) { + t.Parallel() + + testDir := t.TempDir() + _, err := os.Create(fmt.Sprintf("%v/deploy-templates", testDir)) + require.NoError(t, err) + + err = CopyTemplate( + ctrl.LoggerInto(context.Background(), logr.Discard()), + HelmChartDeploymentScriptType, + testDir, + "../../build", + &model.ConfigGoTemplating{}, + ) + require.NoError(t, err) +} + func TestCopyTemplate_ShouldFailOnUnsupportedDeploymemntType(t *testing.T) { t.Parallel() testDir := t.TempDir() cf := &model.ConfigGoTemplating{} - err := CopyTemplate("non-supported-deployment-type", testDir, "../../build", cf) + err := CopyTemplate( + ctrl.LoggerInto(context.Background(), logr.Discard()), + "non-supported-deployment-type", + testDir, + "../../build", + cf, + ) assert.Error(t, err) - assert.Contains(t, err.Error(), "Unsupported deployment type") + assert.Contains(t, err.Error(), "unsupported deployment type") } func TestCopyHelmChartTemplates(t *testing.T) { @@ -110,7 +142,13 @@ func TestCopyTemplate_RPMPackage_ShouldPass(t *testing.T) { GitURL: "https://example.com", } - err := CopyTemplate(RpmPackageDeploymentScriptType, testDir, "../../build", cf) + err := CopyTemplate( + ctrl.LoggerInto(context.Background(), logr.Discard()), + RpmPackageDeploymentScriptType, + testDir, + "../../build", + cf, + ) require.NoError(t, err) chf := fmt.Sprintf("%v/c-name.spec", testDir) @@ -130,44 +168,139 @@ func TestCopyTemplate_RPMPackage_ShouldPass(t *testing.T) { } func TestCopyRpmPackageTemplates(t *testing.T) { - tmp := t.TempDir() + t.Parallel() type args struct { - deploymentScript string - templatesDest string - assetsDir string - config *model.ConfigGoTemplating + templatesDest func(t *testing.T) string + config *model.ConfigGoTemplating } tests := []struct { name string args args wantErr require.ErrorAssertionFunc + want func(t *testing.T, dest string) }{ { - name: "invalid assets dir", + name: "templates created successfully", args: args{ - deploymentScript: HelmChartDeploymentScriptType, - templatesDest: tmp, - assetsDir: "", - config: &model.ConfigGoTemplating{}, + templatesDest: func(t *testing.T) string { + return t.TempDir() + }, + config: &model.ConfigGoTemplating{ + Lang: "java", + Name: "test-application", + }, + }, + wantErr: require.NoError, + want: func(t *testing.T, dest string) { + _, err := os.Stat(fmt.Sprintf("%s/Makefile", dest)) + require.NoError(t, err) + + _, err = os.Stat(fmt.Sprintf("%s/Makefile.kuberocketci", dest)) + require.True(t, os.IsNotExist(err)) + + _, err = os.Stat(fmt.Sprintf("%s/test-application.spec", dest)) + require.NoError(t, err) + + b, err := os.ReadFile(fmt.Sprintf("%s/test-application.spec", dest)) + require.Contains(t, string(b), "test-application") + + b, err = os.ReadFile(fmt.Sprintf("%s/test-application.service", dest)) + require.Contains(t, string(b), "test-application") + + _, err = os.Stat(fmt.Sprintf("%s/test-application.service", dest)) + require.NoError(t, err) + + _, err = os.Stat(fmt.Sprintf("%s/.rpmlintrc.toml", dest)) + require.NoError(t, err) + }, + }, + { + name: "templates with not supported lang created successfully", + args: args{ + templatesDest: func(t *testing.T) string { + return t.TempDir() + }, + config: &model.ConfigGoTemplating{ + Lang: "not-supported", + Name: "test", + }, + }, + wantErr: require.NoError, + want: func(t *testing.T, dest string) { + _, err := os.Stat(fmt.Sprintf("%s/Makefile", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/test.spec", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/test.service", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/.rpmlintrc.toml", dest)) + require.NoError(t, err) + }, + }, + { + name: "makefile already exists", + args: args{ + templatesDest: func(t *testing.T) string { + d := t.TempDir() + + f, err := os.Create(fmt.Sprintf("%s/Makefile", d)) + require.NoError(t, err) + require.NoError(t, f.Close()) + + return d + }, + config: &model.ConfigGoTemplating{ + Lang: "java", + Name: "test", + }, + }, + wantErr: require.NoError, + want: func(t *testing.T, dest string) { + _, err := os.Stat(fmt.Sprintf("%s/Makefile.kuberocketci", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/test.spec", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/test.service", dest)) + require.NoError(t, err) + _, err = os.Stat(fmt.Sprintf("%s/.rpmlintrc.toml", dest)) + require.NoError(t, err) + }, + }, + { + name: "failed to create destination file", + args: args{ + templatesDest: func(t *testing.T) string { + return "invalid-dir" + }, + config: &model.ConfigGoTemplating{}, }, wantErr: func(t require.TestingT, err error, i ...interface{}) { require.Error(t, err) - require.Contains(t, err.Error(), "failed to parse codebase deploy template") + require.Contains(t, err.Error(), "failed to create destination file") }, }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + dest := tt.args.templatesDest(t) + err := CopyRpmPackageTemplates( - tt.args.templatesDest, - tt.args.assetsDir, + ctrl.LoggerInto(context.Background(), logr.Discard()), + dest, + "../../build", tt.args.config, ) tt.wantErr(t, err) + if tt.want != nil { + tt.want(t, dest) + } }) } }