Skip to content

Commit

Permalink
feat: Implement fallback strategy for RPM packaging (#123)
Browse files Browse the repository at this point in the history
- Add default template files for RPM if a language is not supported.
- Update operator to create Makefile.kuberocketci if a Makefile already exists.
  • Loading branch information
zmotso authored and SergK committed Sep 12, 2024
1 parent d5641e6 commit 433cb68
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 49 deletions.
Empty file.
Empty file.
2 changes: 1 addition & 1 deletion controllers/codebase/service/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
74 changes: 43 additions & 31 deletions pkg/util/template.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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")
}
}

Expand Down
167 changes: 150 additions & 17 deletions pkg/util/template_test.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
})
}
}

0 comments on commit 433cb68

Please sign in to comment.