diff --git a/pkg/mount/Dockerfile b/pkg/mount/Dockerfile index caafe8fcc..7c0592239 100644 --- a/pkg/mount/Dockerfile +++ b/pkg/mount/Dockerfile @@ -1,20 +1,27 @@ -FROM linuxkit/alpine:9bcf61f605ef0ce36cc94d59b8eac307862de6e1 AS mirror +FROM linuxkit/alpine:488aa6f5dd2d8121a3c5c5c7a1ecf97c424b96ac AS mirror RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/ RUN apk add --no-cache --initdb -p /out \ alpine-baselayout \ busybox \ - jq \ musl \ sfdisk \ && true RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache +FROM linuxkit/alpine:488aa6f5dd2d8121a3c5c5c7a1ecf97c424b96ac AS build + +RUN apk add --no-cache go musl-dev +ENV GOPATH=/go PATH=$PATH:/go/bin + +COPY *.go /go/src/mountie/ +RUN go-compile.sh /go/src/mountie + FROM scratch ENTRYPOINT [] CMD [] WORKDIR / COPY --from=mirror /out/ / -COPY mount.sh / -CMD ["/bin/sh", "/mount.sh"] -LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var:rshared,rbind"], "capabilities": ["CAP_SYS_ADMIN"], "rootfsPropagation": "shared", "net": "new", "ipc": "new"}' +COPY --from=build /go/bin/mountie usr/bin/mountie +CMD ["/usr/bin/mountie"] +LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var:rshared,rbind", "/:/hostroot"], "capabilities": ["CAP_SYS_ADMIN"], "rootfsPropagation": "shared", "net": "new", "ipc": "new"}' diff --git a/pkg/mount/Makefile b/pkg/mount/Makefile index 2deb1dbf3..6e246442b 100644 --- a/pkg/mount/Makefile +++ b/pkg/mount/Makefile @@ -1,4 +1,4 @@ IMAGE=mount -DEPS=mount.sh +DEPS=mountie.go include ../package.mk diff --git a/pkg/mount/mount.sh b/pkg/mount/mount.sh deleted file mode 100755 index ebf54e8b1..000000000 --- a/pkg/mount/mount.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -MOUNTPOINT="$1" - -[ -z "$MOUNTPOINT" ] && echo "No mountpoint specified" && exit 1 - -mkdir -p "$MOUNTPOINT" - -mount_drive() -{ - # TODO fix for multiple disks, cdroms etc - DEVS="$(find /dev -maxdepth 1 -type b ! -name 'loop*' ! -name 'nbd*' | grep -v '[0-9]$' | sed 's@.*/dev/@@' | sort)" - - for DEV in $DEVS - do - DRIVE="/dev/${DEV}" - - # see if it has a partition table - if sfdisk -d "${DRIVE}" >/dev/null 2>/dev/null - then - # 83 is Linux partition identifier - DATA=$(sfdisk -J "$DRIVE" | jq -e -r '.partitiontable.partitions | map(select(.type=="83")) | .[0].node') - if [ $? -eq 0 ] - then - mount "$DATA" "$MOUNTPOINT" && return - fi - fi - done - - echo "WARNING: Failed to mount a persistent volume (is there one?)" -} - -mount_drive diff --git a/pkg/mount/mountie.go b/pkg/mount/mountie.go new file mode 100644 index 000000000..b304add00 --- /dev/null +++ b/pkg/mount/mountie.go @@ -0,0 +1,192 @@ +package main + +import ( + "bufio" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "sort" + "strings" + "syscall" +) + +var ( + deviceVar, labelVar, uuidVar string +) + +// Fdisk is the JSON output from libfdisk +type Fdisk struct { + PartitionTable struct { + Label string `json:"label"` + ID string `json:"id"` + Device string `json:"device"` + Unit string `json:"unit"` + FirstLBA int `json:"firstlba"` + LastLBA int `json:"lastlba"` + Partitions []struct { + Node string `json:"node"` + Start int `json:"start"` + Size int `json:"size"` + Type string `json:"type"` + UUID string `json:"uuid"` + Name string `json:"name"` + } + } `json:"partitionTable"` +} + +// mount drive/partition to mountpoint +func mount(device, mountpoint string) error { + if out, err := exec.Command("mount", device, mountpoint).CombinedOutput(); err != nil { + return fmt.Errorf("Error mounting %s to %s: %v\n%s", device, mountpoint, err, string(out)) + + } + return nil +} + +func findDevice(pattern string) (string, error) { + out, err := exec.Command("findfs", pattern).Output() + if err != nil { + return "", fmt.Errorf("Error finding device with %s: %v", pattern, err) + } + device := strings.TrimSpace(string(out)) + return device, nil +} + +func findFirst(drives []string) (string, error) { + var first string + + out, err := exec.Command("mount").Output() + if err != nil { + return "", err + } + + mounted := make(map[string]bool) + scanner := bufio.NewScanner(strings.NewReader(string(out))) + for scanner.Scan() { + parts := strings.Split(scanner.Text(), " ") + if _, err := os.Stat(parts[0]); os.IsNotExist(err) { + continue + } + if _, ok := mounted[parts[0]]; !ok { + mounted[parts[0]] = true + } + } + + for _, d := range drives { + err := exec.Command("sfdisk", "-d", d).Run() + if err != nil { + log.Printf("No partition table found on device %s. Skipping.", d) + continue + } + + data, err := exec.Command("sfdisk", "-J", d).Output() + if err != nil { + log.Fatalf("Unable to get drive data for %s from sfdisk: %v", d, err) + } + + f := Fdisk{} + if err := json.Unmarshal(data, &f); err != nil { + return "", fmt.Errorf("Unable to unmarshal partition table from sfdisk: %v", err) + } + + for _, partition := range f.PartitionTable.Partitions { + // ignore anything that isn't a Linux partition + if partition.Type != "83" { + continue + } + if _, ok := mounted[partition.Node]; ok { + log.Printf("%s already mounted. Skipping", partition.Node) + continue + } + first = partition.Node + break + } + } + if first == "" { + return "", fmt.Errorf("No eligible disks found") + } + return first, nil +} + +// return a list of all available drives +func findDrives() []string { + driveKeys := []string{} + ignoreExp := regexp.MustCompile(`^loop.*$|^nbd.*$|^[a-z]+[0-9]+$`) + devs, _ := ioutil.ReadDir("/dev") + for _, d := range devs { + // this probably shouldn't be so hard + // but d.Mode()&os.ModeDevice == 0 doesn't work as expected + mode := d.Sys().(*syscall.Stat_t).Mode + if (mode & syscall.S_IFMT) != syscall.S_IFBLK { + continue + } + // ignore if it matches regexp + if ignoreExp.MatchString(d.Name()) { + continue + } + driveKeys = append(driveKeys, filepath.Join("/dev", d.Name())) + } + sort.Strings(driveKeys) + return driveKeys +} + +func init() { + flag.StringVar(&deviceVar, "device", "", "Name of the device to mount") + flag.StringVar(&labelVar, "label", "", "Label of the device to mount") + flag.StringVar(&uuidVar, "uuid", "", "UUID of the device to mount") +} + +func main() { + flag.Parse() + + var mountpoint string + + switch flag.NArg() { + case 0: + log.Fatal("No mountpoints provided") + case 1: + mountpoint = flag.Args()[0] + case 2: + deviceVar = flag.Args()[0] + mountpoint = flag.Args()[1] + default: + log.Fatalf("Too many arguments") + } + + err := os.MkdirAll(mountpoint, os.ModeDir) + if err != nil { + log.Fatalf("Unable to create mountpoint %s: %v", mountpoint, err) + } + if deviceVar == "" && labelVar != "" { + deviceVar, err = findDevice(fmt.Sprintf("LABEL=%s", labelVar)) + if err != nil { + log.Fatal(err) + } + } + if deviceVar == "" && uuidVar != "" { + deviceVar, err = findDevice(fmt.Sprintf("UUID=%s", uuidVar)) + if err != nil { + log.Fatal(err) + } + } + + if deviceVar == "" { + // find first device + drives := findDrives() + first, err := findFirst(drives) + if err != nil { + log.Fatal(err) + } + deviceVar = first + } + + if err := mount(deviceVar, mountpoint); err != nil { + log.Fatal(err) + } +}