diff --git a/README.md b/README.md index c5637cb0..25a70712 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ the structure of this directory will be better fleshed out. For now, the require are no restrictions on the naming; the image configuration file will specify which image in this directory to use for a particular build. +There are a number of optional directories that may be included in the image configuration directory: + +* `scripts` - If present, all the files in this directory will be included in the built image and automatically + executed during the combustion phase. + ### Running EIB The image configuration directory must be attached to the container at runtime. This serves as both the mechanism diff --git a/pkg/build/build.go b/pkg/build/build.go index 1e76cb97..3af0cd2e 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -46,6 +46,11 @@ func (b *Builder) Build() error { return fmt.Errorf("configuring the welcome message: %w", err) } + err = b.configureScripts() + if err != nil { + return fmt.Errorf("configuring custom scripts: %w", err) + } + err = b.generateCombustionScript() if err != nil { return fmt.Errorf("generating combustion script: %w", err) diff --git a/pkg/build/scripts.go b/pkg/build/scripts.go new file mode 100644 index 00000000..14cbbecb --- /dev/null +++ b/pkg/build/scripts.go @@ -0,0 +1,54 @@ +package build + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + scriptsDir = "scripts" + scriptMode = 0o744 +) + +func (b *Builder) configureScripts() error { + fullScriptsDir := filepath.Join(b.buildConfig.ImageConfigDir, scriptsDir) + + // Nothing to do if the image config dir doesn't have the scripts directory + _, err := os.Stat(fullScriptsDir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("checking for scripts directory at %s: %w", fullScriptsDir, err) + } + + dirListing, err := os.ReadDir(fullScriptsDir) + if err != nil { + return fmt.Errorf("reading the scripts directory at %s: %w", fullScriptsDir, err) + } + + // If the directory exists but there's nothing in it, consider it an error case + if len(dirListing) == 0 { + return fmt.Errorf("no scripts found in directory %s", fullScriptsDir) + } + + for _, scriptEntry := range dirListing { + copyMe := filepath.Join(fullScriptsDir, scriptEntry.Name()) + copyTo := filepath.Join(b.combustionDir, scriptEntry.Name()) + + err = copyFile(copyMe, copyTo) + if err != nil { + return fmt.Errorf("copying script to %s: %w", copyTo, err) + } + err = os.Chmod(copyTo, scriptMode) + if err != nil { + return fmt.Errorf("modifying permissions for script %s: %w", copyTo, err) + } + + // Make sure the combustion main script will execute the newly copied script + b.registerCombustionScript(scriptEntry.Name()) + } + + return nil +} diff --git a/pkg/build/scripts_test.go b/pkg/build/scripts_test.go new file mode 100644 index 00000000..b4e12ab2 --- /dev/null +++ b/pkg/build/scripts_test.go @@ -0,0 +1,99 @@ +package build + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/suse-edge/edge-image-builder/pkg/config" +) + +func TestConfigureScripts(t *testing.T) { + // Setup + // - Testing image config directory + tmpSrcDir, err := os.MkdirTemp("", "eib-") + require.NoError(t, err) + defer os.RemoveAll(tmpSrcDir) + + // - scripts directory to look in + fullScriptsDir := filepath.Join(tmpSrcDir, scriptsDir) + err = os.MkdirAll(fullScriptsDir, os.ModePerm) + require.NoError(t, err) + + // - create sample scripts to be copied + _, err = os.Create(filepath.Join(fullScriptsDir, "foo.sh")) + require.NoError(t, err) + _, err = os.Create(filepath.Join(fullScriptsDir, "bar.sh")) + require.NoError(t, err) + + // - combustion directory into which the scripts should be copied + tmpDestDir, err := os.MkdirTemp("", "eib-") + require.NoError(t, err) + defer os.RemoveAll(tmpDestDir) + + builder := New(nil, &config.BuildConfig{ImageConfigDir: tmpSrcDir}) + builder.combustionDir = tmpDestDir + + // Test + err = builder.configureScripts() + + // Verify + require.NoError(t, err) + + // - make sure the scripts were added to the build directory + foundDirListing, err := os.ReadDir(tmpDestDir) + require.NoError(t, err) + assert.Equal(t, 2, len(foundDirListing)) + + // - make sure the copied files have the right permissions + for _, entry := range foundDirListing { + fullEntryPath := filepath.Join(builder.combustionDir, entry.Name()) + stats, err := os.Stat(fullEntryPath) + require.NoError(t, err) + foundMode := stats.Mode() + assert.Equal(t, "-rwxr--r--", foundMode.String()) + } + + // - make sure entries were added to the combustion scripts list, so they are + // present in the script file that is generated + assert.Equal(t, 2, len(builder.combustionScripts)) +} + +func TestConfigureScriptsNoScriptsDir(t *testing.T) { + // Setup + tmpSrcDir, err := os.MkdirTemp("", "eib-") + require.NoError(t, err) + defer os.RemoveAll(tmpSrcDir) + + builder := New(nil, &config.BuildConfig{ImageConfigDir: tmpSrcDir}) + + // Test + err = builder.configureScripts() + + // Verify + require.NoError(t, err) +} + +func TestConfigureScriptsEmptyScriptsDir(t *testing.T) { + // Setup + // - Testing image config directory + tmpSrcDir, err := os.MkdirTemp("", "eib-") + require.NoError(t, err) + defer os.RemoveAll(tmpSrcDir) + + // - scripts directory to look in + fullScriptsDir := filepath.Join(tmpSrcDir, scriptsDir) + err = os.MkdirAll(fullScriptsDir, os.ModePerm) + require.NoError(t, err) + + builder := New(nil, &config.BuildConfig{ImageConfigDir: tmpSrcDir}) + + // Test + err = builder.configureScripts() + + // Verify + require.Error(t, err) + assert.Contains(t, err.Error(), "no scripts found in directory") +}