diff --git a/pkg/build/build.go b/pkg/build/build.go index a7df8187..18679d07 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -44,14 +44,14 @@ func (b *Builder) Build() error { return fmt.Errorf("configuring users: %w", err) } - err = b.generateCombustionScript() + err = b.processRPMs() if err != nil { - return fmt.Errorf("generating combustion script: %w", err) + return fmt.Errorf("processing RPMs: %w", err) } - err = b.copyRPMs() + err = b.generateCombustionScript() if err != nil { - return fmt.Errorf("copying RPMs over: %w", err) + return fmt.Errorf("generating combustion script: %w", err) } switch b.imageConfig.Image.ImageType { diff --git a/pkg/build/rpm.go b/pkg/build/rpm.go index be556776..de6b5bd2 100644 --- a/pkg/build/rpm.go +++ b/pkg/build/rpm.go @@ -1,19 +1,57 @@ package build import ( + _ "embed" "fmt" "os" "path/filepath" + "strings" + "github.com/suse-edge/edge-image-builder/pkg/fileio" ) -func (b *Builder) getRPMFileNames(rpmSourceDir string) ([]string, error) { +const ( + modifyRPMScriptName = "10-rpm-install.sh" +) + +//go:embed scripts/rpms/10-rpm-install.sh.tpl +var modifyRPMScript string + +func (b *Builder) processRPMs() error { + rpmSourceDir, err := b.generateRPMPath() + if err != nil { + return fmt.Errorf("generating RPM path: %w", err) + } + // Only proceed with processing the RPMs if the directory exists + if rpmSourceDir == "" { + return nil + } + + rpmFileNames, err := getRPMFileNames(rpmSourceDir) + if err != nil { + return fmt.Errorf("getting RPM file names: %w", err) + } + + err = copyRPMs(rpmSourceDir, b.context.CombustionDir, rpmFileNames) + if err != nil { + return fmt.Errorf("copying RPMs over: %w", err) + } + + err = b.writeRPMScript(rpmFileNames) + if err != nil { + return fmt.Errorf("writing the RPM install script %s: %w", modifyRPMScriptName, err) + } + + return nil +} + +func getRPMFileNames(rpmSourceDir string) ([]string, error) { var rpmFileNames []string rpms, err := os.ReadDir(rpmSourceDir) if err != nil { - return nil, fmt.Errorf("reading rpm source dir: %w", err) + return nil, fmt.Errorf("reading RPM source dir: %w", err) } for _, rpmFile := range rpms { @@ -23,34 +61,21 @@ func (b *Builder) getRPMFileNames(rpmSourceDir string) ([]string, error) { } if len(rpmFileNames) == 0 { - return nil, fmt.Errorf("no rpms found") + return nil, fmt.Errorf("no RPMs found") } return rpmFileNames, nil } -func (b *Builder) copyRPMs() error { - rpmSourceDir := filepath.Join(b.context.ImageConfigDir, "rpms") - // Only proceed with copying the RPMs if the directory exists - _, err := os.Stat(rpmSourceDir) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return fmt.Errorf("checking for rpm directory at %s: %w", rpmSourceDir, err) +func copyRPMs(rpmSourceDir string, rpmDestDir string, rpmFileNames []string) error { + if rpmDestDir == "" { + return fmt.Errorf("RPM destination directory cannot be empty") } - rpmDestDir := b.context.CombustionDir - - rpmFileNames, err := b.getRPMFileNames(rpmSourceDir) - if err != nil { - return fmt.Errorf("getting rpm file names: %w", err) - } - for _, rpm := range rpmFileNames { sourcePath := filepath.Join(rpmSourceDir, rpm) destPath := filepath.Join(rpmDestDir, rpm) - err = fileio.CopyFile(sourcePath, destPath) + err := fileio.CopyFile(sourcePath, destPath) if err != nil { return fmt.Errorf("copying file %s: %w", sourcePath, err) } @@ -58,3 +83,37 @@ func (b *Builder) copyRPMs() error { return nil } + +func (b *Builder) writeRPMScript(rpmFileNames []string) error { + values := struct { + RPMs string + }{ + RPMs: strings.Join(rpmFileNames, " "), + } + + writtenFilename, err := b.writeCombustionFile(modifyRPMScriptName, modifyRPMScript, &values) + if err != nil { + return fmt.Errorf("writing RPM script: %w", err) + } + err = os.Chmod(writtenFilename, modifyScriptMode) + if err != nil { + return fmt.Errorf("adjusting permissions: %w", err) + } + + b.registerCombustionScript(modifyRPMScriptName) + + return nil +} + +func (b *Builder) generateRPMPath() (string, error) { + rpmSourceDir := filepath.Join(b.context.ImageConfigDir, "rpms") + _, err := os.Stat(rpmSourceDir) + if err != nil { + if os.IsNotExist(err) { + return "", nil + } + return "", fmt.Errorf("checking for RPM directory at %s: %w", rpmSourceDir, err) + } + + return rpmSourceDir, nil +} diff --git a/pkg/build/rpm_test.go b/pkg/build/rpm_test.go index 9866ecb1..9c1f860a 100644 --- a/pkg/build/rpm_test.go +++ b/pkg/build/rpm_test.go @@ -1,6 +1,7 @@ package build import ( + "io/fs" "os" "path/filepath" "testing" @@ -9,123 +10,191 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetRPMFileNames(t *testing.T) { - // Setup - context, err := NewContext("../config/testdata", "", true) +func setupRPMSourceDir(t *testing.T, addFiles bool) (tmpDir string, rpmSourceDir string, teardown func()) { + tmpDir, err := os.MkdirTemp("", "eib-RPM-") require.NoError(t, err) - defer func() { - assert.NoError(t, CleanUpBuildDir(context)) - }() - builder := &Builder{ - context: context, - } + rpmSourceDir = filepath.Join(tmpDir, "rpms") + err = os.Mkdir(rpmSourceDir, 0o755) + require.NoError(t, err) - rpmSourceDir := filepath.Join(context.ImageConfigDir, "rpms") + var file1 *os.File + var file2 *os.File + if addFiles { + file1Path := filepath.Join(rpmSourceDir, "rpm1.rpm") + file1, err = os.Create(file1Path) + require.NoError(t, err) - file1Path := filepath.Join(rpmSourceDir, "rpm1.rpm") - defer os.Remove(file1Path) - file1, err := os.Create(file1Path) - require.NoError(t, err) + file2Path := filepath.Join(rpmSourceDir, "rpm2.rpm") + file2, err = os.Create(file2Path) + require.NoError(t, err) + } - file2Path := filepath.Join(rpmSourceDir, "rpm2.rpm") - defer os.Remove(file2Path) - file2, err := os.Create(file2Path) - require.NoError(t, err) + return tmpDir, rpmSourceDir, func() { + if addFiles { + assert.NoError(t, file1.Close()) + assert.NoError(t, file2.Close()) + } + assert.NoError(t, os.RemoveAll(tmpDir)) + } +} + +func TestGetRPMFileNames(t *testing.T) { + // Setup + _, rpmSourceDir, teardown := setupRPMSourceDir(t, true) + defer teardown() // Test - rpmFileNames, err := builder.getRPMFileNames(rpmSourceDir) + rpmFileNames, err := getRPMFileNames(rpmSourceDir) // Verify require.NoError(t, err) assert.Contains(t, rpmFileNames, "rpm1.rpm") assert.Contains(t, rpmFileNames, "rpm2.rpm") - - // Cleanup - assert.NoError(t, file1.Close()) - assert.NoError(t, file2.Close()) } func TestCopyRPMs(t *testing.T) { // Setup - context, err := NewContext("../config/testdata", "", true) + tmpDir, rpmSourceDir, teardown := setupRPMSourceDir(t, true) + defer teardown() + + tmpDestDir := filepath.Join(tmpDir, "temp-dest-dir") + err := os.Mkdir(tmpDestDir, 0o755) require.NoError(t, err) - defer func() { - assert.NoError(t, CleanUpBuildDir(context)) - }() - builder := &Builder{ - context: context, - } + // Test + err = copyRPMs(rpmSourceDir, tmpDestDir, []string{"rpm1.rpm", "rpm2.rpm"}) - rpmSourceDir := filepath.Join(context.ImageConfigDir, "rpms") + // Verify + require.NoError(t, err) - file1Path := filepath.Join(rpmSourceDir, "rpm1.rpm") - defer os.Remove(file1Path) - file1, err := os.Create(file1Path) + _, err = os.Stat(filepath.Join(tmpDestDir, "rpm1.rpm")) require.NoError(t, err) - file2Path := filepath.Join(rpmSourceDir, "rpm2.rpm") - defer os.Remove(file2Path) - file2, err := os.Create(file2Path) + _, err = os.Stat(filepath.Join(tmpDestDir, "rpm2.rpm")) require.NoError(t, err) +} + +func TestGetRPMFileNamesNoRPMs(t *testing.T) { + // Setup + _, rpmSourceDir, teardown := setupRPMSourceDir(t, false) + defer teardown() // Test - err = builder.copyRPMs() + rpmFileNames, err := getRPMFileNames(rpmSourceDir) // Verify + require.ErrorContains(t, err, "no RPMs found") + + assert.Empty(t, rpmFileNames) +} + +func TestCopyRPMsNoRPMDestDir(t *testing.T) { + // Setup + _, rpmSourceDir, teardown := setupRPMSourceDir(t, true) + defer teardown() + + // Test + err := copyRPMs(rpmSourceDir, "", []string{"rpm1.rpm", "rpm2.rpm"}) + + // Verify + require.ErrorContains(t, err, "RPM destination directory cannot be empty") +} + +func TestCopyRPMsNoRPMSrcDir(t *testing.T) { + // Setup + tmpDestDir, _, teardown := setupRPMSourceDir(t, true) + defer teardown() + + // Test + err := copyRPMs("", tmpDestDir, []string{"rpm1.rpm", "rpm2.rpm"}) + + // Verify + require.ErrorContains(t, err, "opening source file") +} + +func TestWriteRPMScript(t *testing.T) { + // Setup + context, err := NewContext("", "", true) require.NoError(t, err) + defer func() { + assert.NoError(t, CleanUpBuildDir(context)) + }() - _, err = os.Stat(filepath.Join(builder.context.CombustionDir, "rpm1.rpm")) + builder := Builder{context: context} + + // Test + err = builder.writeRPMScript([]string{"rpm1.rpm", "rpm2.rpm"}) + + // Verify require.NoError(t, err) - _, err = os.Stat(filepath.Join(builder.context.CombustionDir, "rpm2.rpm")) + expectedFilename := filepath.Join(builder.context.CombustionDir, modifyRPMScriptName) + foundBytes, err := os.ReadFile(expectedFilename) require.NoError(t, err) - // Cleanup - assert.NoError(t, file1.Close()) - assert.NoError(t, file2.Close()) + stats, err := os.Stat(expectedFilename) + require.NoError(t, err) + assert.Equal(t, fs.FileMode(modifyScriptMode), stats.Mode()) + + foundContents := string(foundBytes) + assert.Contains(t, foundContents, "rpm1.rpm") + assert.Contains(t, foundContents, "rpm2.rpm") } -func TestGetRPMFileNamesNoRPMs(t *testing.T) { +func TestProcessRPMs(t *testing.T) { // Setup - context, err := NewContext("../config/testdata", "", true) + tmpDir, _, teardown := setupRPMSourceDir(t, true) + defer teardown() + + context, err := NewContext(tmpDir, "", true) require.NoError(t, err) defer func() { assert.NoError(t, CleanUpBuildDir(context)) }() - builder := &Builder{ - context: context, - } - - rpmSourceDir := filepath.Join(context.ImageConfigDir, "rpms") + builder := Builder{context: context} // Test - rpmFileNames, err := builder.getRPMFileNames(rpmSourceDir) + err = builder.processRPMs() // Verify - require.ErrorContains(t, err, "no rpms found") + require.NoError(t, err) - assert.Empty(t, rpmFileNames) + _, err = os.Stat(filepath.Join(builder.context.CombustionDir, "rpm1.rpm")) + require.NoError(t, err) + + _, err = os.Stat(filepath.Join(builder.context.CombustionDir, "rpm2.rpm")) + require.NoError(t, err) + + expectedFilename := filepath.Join(builder.context.CombustionDir, modifyRPMScriptName) + foundBytes, err := os.ReadFile(expectedFilename) + require.NoError(t, err) + + foundContents := string(foundBytes) + assert.Contains(t, foundContents, "rpm1.rpm") + assert.Contains(t, foundContents, "rpm2.rpm") } -func TestCopyRPMsNoRPMDir(t *testing.T) { +func TestGenerateRPMPath(t *testing.T) { // Setup - context, err := NewContext("../config/ThisDirDoesNotExist", "", true) + tmpDir, expectedPath, teardown := setupRPMSourceDir(t, false) + defer teardown() + + context, err := NewContext(tmpDir, "", true) require.NoError(t, err) defer func() { assert.NoError(t, CleanUpBuildDir(context)) }() - builder := &Builder{ - context: context, - } + builder := Builder{context: context} // Test - err = builder.copyRPMs() + generatedPath, err := builder.generateRPMPath() // Verify require.NoError(t, err) + + assert.Equal(t, expectedPath, generatedPath) } diff --git a/pkg/build/scripts/rpms/10-rpm-install.sh.tpl b/pkg/build/scripts/rpms/10-rpm-install.sh.tpl new file mode 100644 index 00000000..b92445d7 --- /dev/null +++ b/pkg/build/scripts/rpms/10-rpm-install.sh.tpl @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +# Template Fields +# RPMs - A string that contains all of the RPMs present in the user created config directory, separated by spaces. + +rpm -ivh --nosignature {{.RPMs}} \ No newline at end of file diff --git a/pkg/config/testdata/rpms/.gitkeep b/pkg/config/testdata/rpms/.gitkeep deleted file mode 100644 index e69de29b..00000000