Skip to content

Commit

Permalink
added ability to copy files into the built image's filesystem (#474)
Browse files Browse the repository at this point in the history
* added ability to copy files into the built image's filesystem

* changed os-files implementation to use the existing CopyFiles function

* incorporated PR feedback
  • Loading branch information
jdob authored Jun 26, 2024
1 parent 6aa4c22 commit a8d81ac
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 0 deletions.
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

## General

* Added the ability to automatically copy files into the built images filesystem

## API

### Image Definition Changes

### Image Configuration Directory Changes

* An optional directory named `os-files` may be included to copy files into the resulting image's filesystem at runtime

## Bug Fixes

---
Expand Down
18 changes: 18 additions & 0 deletions docs/building-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,24 @@ the built image and used to register with Elemental on boot.
> ```podman run``` command. For more info on why this is required, please see
> [Package resolution design](design/pkg-resolution.md#running-the-eib-container).

## Operating System Files

Files placed in the `os-files` directory in the image configuration directory will be automatically copied
into the filesystem of the built image. The exact directory structure will be retained when they are copied.
For example, if a file exists in a subdirectory named `os-files/etc`, it will be placed in the `/etc` directory
of the built image.

If the `os-files` directory exists, it cannot be empty.

```bash
.
├── definition.yaml
└── os-files
└── etc
└── ssh
└── sshd_config
```

## Custom

EIB has the ability to bundle in custom scripts that will be run during the combustion phase when a node is
Expand Down
4 changes: 4 additions & 0 deletions pkg/combustion/combustion.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func (c *Combustion) Configure(ctx *image.Context) error {
name: rpmComponentName,
runnable: c.configureRPMs,
},
{
name: osFilesComponentName,
runnable: configureOSFiles,
},
{
name: systemdComponentName,
runnable: configureSystemd,
Expand Down
77 changes: 77 additions & 0 deletions pkg/combustion/osfiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package combustion

import (
_ "embed"
"fmt"
"os"
"path/filepath"

"github.com/suse-edge/edge-image-builder/pkg/fileio"
"github.com/suse-edge/edge-image-builder/pkg/image"
"github.com/suse-edge/edge-image-builder/pkg/log"
"go.uber.org/zap"
)

const (
osFilesComponentName = "os files"
osFilesConfigDir = "os-files"
osFilesScriptName = "19-copy-os-files.sh"
osFilesLogFile = "copy-os-files.log"
)

var (
//go:embed templates/19-copy-os-files.sh
osFilesScript string
)

func configureOSFiles(ctx *image.Context) ([]string, error) {
if !isComponentConfigured(ctx, osFilesConfigDir) {
log.AuditComponentSkipped(osFilesComponentName)
zap.S().Info("skipping os files component, no files provided")
return nil, nil
}

if err := copyOSFiles(ctx); err != nil {
log.AuditComponentFailed(osFilesComponentName)
return nil, err
}

if err := writeOSFilesScript(ctx); err != nil {
log.AuditComponentFailed(osFilesComponentName)
return nil, err
}

log.AuditComponentSuccessful(osFilesComponentName)
return []string{osFilesScriptName}, nil
}

func copyOSFiles(ctx *image.Context) error {
srcDirectory := filepath.Join(ctx.ImageConfigDir, osFilesConfigDir)
destDirectory := filepath.Join(ctx.CombustionDir, osFilesConfigDir)

dirEntries, err := os.ReadDir(srcDirectory)
if err != nil {
return fmt.Errorf("reading the os files directory at %s: %w", srcDirectory, err)
}

// If the directory exists but there's nothing in it, consider it an error case
if len(dirEntries) == 0 {
return fmt.Errorf("no files found in directory %s", srcDirectory)
}

if err := fileio.CopyFiles(srcDirectory, destDirectory, "", true); err != nil {
return fmt.Errorf("copying os-files: %w", err)
}

return nil
}

func writeOSFilesScript(ctx *image.Context) error {
osFilesScriptFilename := filepath.Join(ctx.CombustionDir, osFilesScriptName)

if err := os.WriteFile(osFilesScriptFilename, []byte(osFilesScript), fileio.ExecutablePerms); err != nil {
return fmt.Errorf("writing os files script %s: %w", osFilesScriptFilename, err)
}

return nil
}
71 changes: 71 additions & 0 deletions pkg/combustion/osfiles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package combustion

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/suse-edge/edge-image-builder/pkg/image"
)

func setupOsFilesConfigDir(t *testing.T, empty bool) (ctx *image.Context, teardown func()) {
ctx, teardown = setupContext(t)

testOsFilesDir := filepath.Join(ctx.ImageConfigDir, osFilesConfigDir)
err := os.Mkdir(testOsFilesDir, 0o755)
require.NoError(t, err)

if !empty {
nestedOsFilesDir := filepath.Join(testOsFilesDir, "etc", "ssh")
err = os.MkdirAll(nestedOsFilesDir, 0o755)
require.NoError(t, err)

testFile := filepath.Join(nestedOsFilesDir, "test-config-file")
_, err = os.Create(testFile)
require.NoError(t, err)
}

return
}

func TestConfigureOSFiles(t *testing.T) {
// Setup
ctx, teardown := setupOsFilesConfigDir(t, false)
defer teardown()

// Test
scriptNames, err := configureOSFiles(ctx)

// Verify
require.NoError(t, err)

assert.Equal(t, []string{osFilesScriptName}, scriptNames)

// -- Combustion Script
expectedCombustionScript := filepath.Join(ctx.CombustionDir, osFilesScriptName)
contents, err := os.ReadFile(expectedCombustionScript)
require.NoError(t, err)
assert.Contains(t, string(contents), "cp -R")

// -- Files
expectedFile := filepath.Join(ctx.CombustionDir, osFilesConfigDir, "etc", "ssh", "test-config-file")
assert.FileExists(t, expectedFile)
}

func TestConfigureOSFiles_EmptyDirectory(t *testing.T) {
// Setup
ctx, teardown := setupOsFilesConfigDir(t, true)
defer teardown()

// Test
scriptName, err := configureOSFiles(ctx)

// Verify
assert.Nil(t, scriptName)

srcDirectory := filepath.Join(ctx.ImageConfigDir, osFilesConfigDir)
assert.EqualError(t, err, fmt.Sprintf("no files found in directory %s", srcDirectory))
}
4 changes: 4 additions & 0 deletions pkg/combustion/templates/19-copy-os-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
set -euo pipefail

cp -R ./os-files/* /

0 comments on commit a8d81ac

Please sign in to comment.