Skip to content

Commit

Permalink
Raw image modification implementation (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdob authored Oct 26, 2023
1 parent c8d9d7e commit c486ee2
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 14 deletions.
9 changes: 7 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ RUN go build ./cmd/eib


# ----- Deliverable Image -----
FROM registry.suse.com/bci/bci-base:15.5
FROM opensuse/leap:15.5

RUN zypper install -y xorriso
# Dependency uses by line
# 1. ISO image building
# 2. RAW image modification on x86_64
RUN zypper install -y \
xorriso \
libguestfs kernel-default e2fsprogs parted gptfdisk btrfsprogs

COPY --from=0 /src/eib /bin/eib

Expand Down
23 changes: 21 additions & 2 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,18 @@ func (b *Builder) Build() error {
return fmt.Errorf("generating combustion script: %w", err)
}

err = b.buildIsoImage()
switch b.imageConfig.Image.ImageType {
case config.ImageTypeISO:
err = b.buildIsoImage()
case config.ImageTypeRAW:
err = b.buildRawImage()
default:
err = fmt.Errorf("invalid imageType value specified, must be either \"%s\" or \"%s\"",
config.ImageTypeISO, config.ImageTypeRAW)
}

if err != nil {
return fmt.Errorf("error building modified ISO: %w", err)
return err
}

err = b.cleanUpBuildDir()
Expand Down Expand Up @@ -154,3 +163,13 @@ func (b *Builder) registerCombustionScript(scriptName string) {

b.combustionScripts = append(b.combustionScripts, scriptName)
}

func (b *Builder) generateOutputImageFilename() string {
filename := filepath.Join(b.buildConfig.ImageConfigDir, b.imageConfig.Image.OutputImageName)
return filename
}

func (b *Builder) generateBaseImageFilename() string {
filename := filepath.Join(b.buildConfig.ImageConfigDir, "images", b.imageConfig.Image.BaseImage)
return filename
}
11 changes: 3 additions & 8 deletions pkg/build/iso.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (b *Builder) buildIsoImage() error {
}

func (b *Builder) deleteExistingOutputIso() error {
outputFilename := b.generateOutputIsoFilename()
outputFilename := b.generateOutputImageFilename()
err := os.Remove(outputFilename)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("error deleting file %s: %w", outputFilename, err)
Expand All @@ -63,8 +63,8 @@ func (b *Builder) createXorrisoCommand() (*exec.Cmd, *os.File, error) {
}

func (b *Builder) generateXorrisoArgs() []string {
indevPath := filepath.Join(b.buildConfig.ImageConfigDir, "images", b.imageConfig.Image.BaseImage)
outdevPath := filepath.Join(b.buildConfig.ImageConfigDir, b.imageConfig.Image.OutputImageName)
indevPath := b.generateBaseImageFilename()
outdevPath := b.generateOutputImageFilename()
mapDir := b.combustionDir

args := fmt.Sprintf(xorrisoArgsBase, indevPath, outdevPath, mapDir)
Expand All @@ -78,8 +78,3 @@ func (b *Builder) generateIsoLogFilename() string {
logFilename := filepath.Join(b.eibBuildDir, filename)
return logFilename
}

func (b *Builder) generateOutputIsoFilename() string {
filename := filepath.Join(b.buildConfig.ImageConfigDir, b.imageConfig.Image.OutputImageName)
return filename
}
4 changes: 2 additions & 2 deletions pkg/build/iso_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestDeleteExistingImage(t *testing.T) {
}
builder := New(&imageConfig, &buildConfig)

_, err = os.Create(builder.generateOutputIsoFilename())
_, err = os.Create(builder.generateOutputImageFilename())
require.NoError(t, err)

// Test
Expand All @@ -58,7 +58,7 @@ func TestDeleteExistingImage(t *testing.T) {
// Verify
require.NoError(t, err)

_, err = os.Stat(builder.generateOutputIsoFilename())
_, err = os.Stat(builder.generateOutputImageFilename())
require.Error(t, err)
require.True(t, os.IsNotExist(err))
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/build/raw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package build

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

const (
copyExec = "/bin/cp"
modifyScriptName = "modify-raw-image.sh"
)

//go:embed scripts/modify-raw-image.sh
var modifyRawImageScript string

func (b *Builder) buildRawImage() error {
cmd := b.createRawImageCopyCommand()
err := cmd.Run()
if err != nil {
return fmt.Errorf("copying the base image %s to the output image location %s: %w",
b.imageConfig.Image.BaseImage, b.generateOutputImageFilename(), err)
}

err = b.writeModifyScript()
if err != nil {
return fmt.Errorf("writing the image modification script: %w", err)
}

cmd = b.createModifyCommand()
err = cmd.Run()
if err != nil {
return fmt.Errorf("running the image modification script: %w", err)
}

return nil
}

func (b *Builder) createRawImageCopyCommand() *exec.Cmd {
baseImagePath := b.generateBaseImageFilename()
outputImagePath := b.generateOutputImageFilename()

cmd := exec.Command(copyExec, baseImagePath, outputImagePath)
return cmd
}

func (b *Builder) writeModifyScript() error {
imageToModify := b.generateOutputImageFilename()
contents := fmt.Sprintf(modifyRawImageScript, imageToModify, b.combustionDir)

scriptPath := filepath.Join(b.buildConfig.BuildDir, modifyScriptName)
err := os.WriteFile(scriptPath, []byte(contents), os.ModePerm)
if err != nil {
return fmt.Errorf("writing file %s: %w", scriptPath, err)
}

return nil
}

func (b *Builder) createModifyCommand() *exec.Cmd {
scriptPath := filepath.Join(b.buildConfig.BuildDir, modifyScriptName)
cmd := exec.Command(scriptPath)
return cmd
}
89 changes: 89 additions & 0 deletions pkg/build/raw_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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 TestCreateRawImageCopyCommand(t *testing.T) {
// Setup
imageConfig := config.ImageConfig{
Image: config.Image{
BaseImage: "base-image",
OutputImageName: "build-image",
},
}
buildConfig := config.BuildConfig{
ImageConfigDir: "config-dir",
}
builder := New(&imageConfig, &buildConfig)

// Test
cmd := builder.createRawImageCopyCommand()

// Verify
require.NotNil(t, cmd)

assert.Equal(t, copyExec, cmd.Path)
expectedArgs := []string {
copyExec,
builder.generateBaseImageFilename(),
builder.generateOutputImageFilename(),
}
assert.Equal(t, expectedArgs, cmd.Args)
}

func TestWriteModifyScript(t *testing.T) {
// Setup
tmpDir, err := os.MkdirTemp("", "eib-")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

imageConfig := config.ImageConfig{
Image: config.Image{
OutputImageName: "output-image",
},
}
buildConfig := config.BuildConfig{
ImageConfigDir: "config-dir",
BuildDir: tmpDir,
}
builder := New(&imageConfig, &buildConfig)
builder.combustionDir = "combustion-dir"

// Test
err = builder.writeModifyScript()

// Verify
require.NoError(t, err)

expectedFilename := filepath.Join(tmpDir, modifyScriptName)
foundBytes, err := os.ReadFile(expectedFilename)
require.NoError(t, err)

foundContents := string(foundBytes)
assert.Contains(t, foundContents, "guestfish --rw -a config-dir/output-image")
assert.Contains(t, foundContents, "copy-in combustion-dir")
}

func TestCreateModifyCommand(t *testing.T) {
// Setup
buildConfig := config.BuildConfig{
BuildDir: "build-dir",
}
builder := New(nil, &buildConfig)

// Test
cmd := builder.createModifyCommand()

// Verify
require.NotNil(t, cmd)

expectedPath := filepath.Join("build-dir", modifyScriptName)
assert.Equal(t, expectedPath, cmd.Path)
}
32 changes: 32 additions & 0 deletions pkg/build/scripts/modify-raw-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
set -euo pipefail

# Substitution Fields
# 1. Full path to the image to modify
# 2. Full path to the combustion directory

# Guestfish Commands Explanation
#
# sh "btrfs property set / ro false"
# - Enables write access to the read only filesystem
#
# copy-in _s /
# - Copies the combustion directory into the root of the image
# - _s should be populated with the full path to the built combustion directory
#
# sh "btrfs filesystem label / INSTALL"
# - As of Oct 25, 2023, combustion only checks volumes of certain names for the
# /combustion directory. The SLE Micro raw image sets the root partition name to
# "ROOT", which isn't one of the checked volume names. This line changes the
# label to "INSTALL" (the same as the ISO installer uses) so it's picked up
# when combustion runs.
#
# sh "btrfs property set / ro true"
# - Resets the filesystem to read only

guestfish --rw -a %s -i <<'EOF'
sh "btrfs property set / ro false"
copy-in %s /
sh "btrfs filesystem label / INSTALL"
sh "btrfs property set / ro true"
EOF
5 changes: 5 additions & 0 deletions pkg/config/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (
"gopkg.in/yaml.v3"
)

const (
ImageTypeISO = "iso"
ImageTypeRAW = "raw"
)

type ImageConfig struct {
APIVersion string `yaml:"apiVersion"`
Image Image `yaml:"image"`
Expand Down

0 comments on commit c486ee2

Please sign in to comment.