From 809fb487ddd535b6d04ef4bdd36d381f0ffe8eba Mon Sep 17 00:00:00 2001 From: Vladimir Romashchenko Date: Mon, 18 Oct 2021 03:49:24 -0400 Subject: [PATCH] feat(map_image): Replaced kpartx with losetup losetup is part of util-linux and should be preinstalled on most of linux distributions --- Dockerfile | 1 - Dockerfile.release | 1 - README.md | 11 ++--- pkg/builder/step_map_image.go | 76 +++++++++++++++++------------------ 4 files changed, 43 insertions(+), 46 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0eb78e1c..b2a79dda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 \ diff --git a/Dockerfile.release b/Dockerfile.release index 907e911e..8dcbdee5 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -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 \ diff --git a/README.md b/README.md index c275f2bb..492a7f53 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 @@ -111,6 +110,7 @@ Build the `samples/raspbian_golang.json` Packer image 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 \ @@ -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 \ diff --git a/pkg/builder/step_map_image.go b/pkg/builder/step_map_image.go index ca98339d..2a7077da 100644 --- a/pkg/builder/step_map_image.go +++ b/pkg/builder/step_map_image.go @@ -3,7 +3,9 @@ package builder import ( "context" "fmt" + "os" "os/exec" + "regexp" "strings" "github.com/hashicorp/packer-plugin-sdk/multistep" @@ -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) @@ -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))) + } + } + } }