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

feat(map_image): Replaced kpartx with losetup #115

Merged
merged 1 commit into from
Oct 19, 2021
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
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ FROM docker.io/library/ubuntu:focal
RUN apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
qemu-user-static \
kpartx \
unzip \
wget \
curl \
Expand Down
1 change: 0 additions & 1 deletion Dockerfile.release
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ FROM docker.io/library/ubuntu:focal
RUN apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
qemu-user-static \
kpartx \
unzip \
wget \
curl \
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@ The plugin runs the provisioners in a chroot environment. Binary execution is d

## Dependencies:
This builder uses the following shell commands:
- `kpartx` - mapping the partitions to mountable devices
- `qemu-user-static` - Executing arm binaries

To install the needed binaries on derivatives of the Debian Linux variant:
```shell
sudo apt install kpartx qemu-user-static
sudo apt install qemu-user-static
```

Fedora:
```shell
sudo dnf install kpartx qemu-user-static
sudo dnf install qemu-user-static
```

Archlinux:
```shell
pacman -S qemu-arm-static multipath-tools
pacman -S qemu-arm-static
```

Other commands that are used are (that should already be installed) : mount, umount, cp, ls, chroot.
Expand Down Expand Up @@ -97,7 +96,7 @@ That's it! Flash it and run!

## Running with Docker
### Prerequisites
Your environment must be running docker daemon with the `devicemapper` [storage driver](https://docs.docker.com/storage/storagedriver/select-storage-driver/) as `kpartx` does not work with the newer `overlay2` prefferred driver. `devicemapper` is [not available on Docker for Mac / Windows](https://docs.docker.com/storage/storagedriver/select-storage-driver/#docker-desktop-for-mac-and-docker-desktop-for-windows).
Docker needs capability of creating new devices on host machine, so it can create `/dev/loop*` and mount image into it. While it may be possible to accomplish with multiple `--device-cgroup-rule` and `--add-cap`, it's much easier to use `--privileged` flag to accomplish that. Even so, it is considered bad practice to do so, do it with extra precautions. Also because of those requirements rootless will not work for this container.

### Option 1: Clone this repo and build the Docker image locally

Expand All @@ -111,6 +110,7 @@ Build the `samples/raspbian_golang.json` Packer image
docker run \
--rm \
--privileged \
-v /dev:/dev \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with --privileged docker doesn't update /dev filesystem automatically, and because of that, newly created partitions don't appear inside container. This is really old bug and bind mounting of /dev seems to be official solution

-v ${PWD}:/build:ro \
-v ${PWD}/packer_cache:/build/packer_cache \
-v ${PWD}/output-arm-image:/build/output-arm-image \
Expand All @@ -124,6 +124,7 @@ Alternatively, you can use the `docker.pkg.github.com/solo-io/packer-plugin-arm-
docker run \
--rm \
--privileged \
-v /dev:/dev \
-v ${PWD}:/build:ro \
-v ${PWD}/packer_cache:/build/packer_cache \
-v ${PWD}/output-arm-image:/build/output-arm-image \
Expand Down
76 changes: 37 additions & 39 deletions pkg/builder/step_map_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package builder
import (
"context"
"fmt"
"os"
"os/exec"
"regexp"
"strings"

"github.com/hashicorp/packer-plugin-sdk/multistep"
Expand All @@ -16,55 +18,40 @@ type stepMapImage struct {
}

func (s *stepMapImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
// Read our value and assert that it is they type we want
// Read our value and assert that it is the type we want
image := state.Get(s.ImageKey).(string)
ui := state.Get("ui").(packer.Ui)

ui.Message(fmt.Sprintf("mapping %s", image))
// if run(state, fmt.Sprintf(
// "kpartx -s -a %s",
// image)) != nil {
// return multistep.ActionHalt
//}

out, err := exec.Command("kpartx", "-s", "-a", "-v", image).CombinedOutput()
ui.Say(fmt.Sprintf("kpartx -s -a -v %s", image))

// out, err := exec.Command("kpartx", "-l", image).CombinedOutput()
// ui.Say(fmt.Sprintf("kpartx -l: %s", string(out)))
// Create loopback device
// -P (--partscan) creates a partitioned loop device
// -f (--find) finds first unused loop device
// --show outputs used loop device path
// Output example:
// /dev/loop10
out, err := exec.Command("losetup", "--show", "-f", "-P", image).CombinedOutput()
ui.Say(fmt.Sprintf("losetup --show -f -P %s", image))
if err != nil {
ui.Error(fmt.Sprintf("error kaprts -l %v: %s", err, string(out)))
ui.Error(fmt.Sprintf("error losetup --show -f -P %v: %s", err, string(out)))
s.Cleanup(state)
return multistep.ActionHalt
}
path := strings.TrimSpace(string(out))
loop := strings.Split(path, "/")[2]

// get the loopback device for the partitions
// kpartx -l output looks like this:
/*
loop2p1 : 0 85045 /dev/loop2 8192
loop2p2 : 0 3534848 /dev/loop2 94208
*/
/*
kpartx -a -v output looks like this:

add map loop20p1 (254:22): 0 88262 linear 7:20 8192
add map loop20p2 (254:23): 0 3538944 linear 7:20 98304
*/
lines := strings.Split(string(out), "\n")

// Look for all partitions of created loopback
var partitions []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
device := strings.Split(string(line), " ")
if len(device) != 9 {
ui.Error("bad kpartx output: " + string(out))
s.Cleanup(state)
return multistep.ActionHalt
files, err := os.ReadDir("/dev/")
if err != nil {
ui.Error(fmt.Sprintf("Couldn't list devices in /dev/"))
s.Cleanup(state)
return multistep.ActionHalt
}
for _, file := range files {
if strings.HasPrefix(file.Name(), loop+"p") {
partitions = append(partitions, "/dev/"+file.Name())
}
partitions = append(partitions, "/dev/mapper/"+device[2])
}

state.Put(s.ResultKey, partitions)
Expand All @@ -73,6 +60,17 @@ func (s *stepMapImage) Run(_ context.Context, state multistep.StateBag) multiste
}

func (s *stepMapImage) Cleanup(state multistep.StateBag) {
image := state.Get(s.ImageKey).(string)
run(context.TODO(), state, fmt.Sprintf("kpartx -d %s", image))
switch partitions := state.Get(s.ResultKey).(type) {
case nil:
return
case []string:
if len(partitions) > 0 {
// Convert /dev/loop10p1 into /dev/loop10
re := regexp.MustCompile("/dev/loop[0-9]+")
loop := re.Find([]byte(partitions[0]))
if loop != nil {
run(context.TODO(), state, fmt.Sprintf("losetup -d %s", string(loop)))
}
}
}
}