Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ability to copy files into the built image's filesystem #474

Merged
merged 3 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -113,6 +113,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/* /
jdob marked this conversation as resolved.
Show resolved Hide resolved