diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..44f341e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +# Top-most EditorConfig file +root = true + +[*] +indent_style = tab +indent_size = 4 diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 0a94980..1baee9a 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -10,7 +10,7 @@ on: - "v*.*.*" pull_request: branches: - - "main" + - "master" jobs: push-store-image: @@ -21,25 +21,25 @@ jobs: steps: - name: "Checkout Repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Login to GitHub Container Registry" if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ghcr.io/${{github.actor}}/${{github.repository}} tags: | @@ -52,7 +52,7 @@ jobs: type=sha - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/Dockerfile b/Dockerfile index ddb09be..ed1fb34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.19 -ENV DEBIAN_FRONTEND noninteractive +ENV DEBIAN_FRONTEND=noninteractive WORKDIR /go/src/github.com/allfro/device-volume-driver @@ -10,11 +10,13 @@ COPY . . RUN CGO_ENABLED=1 GOOS=linux go build -ldflags "-linkmode external -extldflags -static" -o /dvd -FROM alpine +FROM docker:latest + +RUN apk add bash WORKDIR / COPY --from=0 /dvd /dvd +COPY /entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/dvd"] - +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index e918022..3157b08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,9 @@ version: "3.8" - services: dmm: - image: docker - entrypoint: docker - command: | - run - -i - --name device-manager - --restart always - --privileged - --cgroupns=host - --pid=host - --userns=host - -v /sys:/host/sys - -v /var/run/docker.sock:/var/run/docker.sock - ndouba/device-mapping-manager + image: ghcr.io/allfro/allfro/device-mapping-manager:nightly + environment: + DOCKER_IAMGE: ghcr.io/allfro/allfro/device-mapping-manager:nightly # Set this to the same as 'image' above volumes: - /var/run/docker.sock:/var/run/docker.sock diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..6646f62 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -e + + +script_dir=$(cd $(dirname ${BASH_SOURCE[@]}) && pwd) +container_name="swarm-device-manager" + + +# All printed log lines from this script should be formatted with this function +print_log() { + local timestamp="$(date +'%Y-%m-%d %H:%M:%S %z')" + local pid="$$" + local level="$1" + local message="${@:2}" + echo "[${timestamp}] [${pid}] [${level^^}] ${message}" +} + + +# Ensure docker socket is available +if [ ! -S /var/run/docker.sock ]; then + print_log "error" "Missing Docker socket. Ensure you run this container mounting '/var/run/docker.sock'. Exit!" + exit 1 +fi + + +# Re-run this as a docker container with elevated privileges +if [ ! -d /host/sys ]; then + # Ensure image version is available + if [ "X${DOCKER_IAMGE}" = "X" ]; then + print_log "error" "Missing required 'DOCKER_IAMGE' variable. Exit!" + exit 1 + fi + + # Check if container is already running. Stop it + if docker ps --filter "name=^${container_name}$" | grep -q "${container_name}"; then + docker stop "${container_name}" &> /dev/null || true + fi + + # Run docker container + print_log "info" "Running privileged container '${container_name}'..." + exec docker run \ + --rm \ + -i \ + --name "${container_name}" \ + --privileged \ + --cgroupns=host \ + --pid=host \ + --userns=host \ + -v /sys:/host/sys \ + -v /var/run/docker.sock:/var/run/docker.sock \ + "${DOCKER_IAMGE:?}" +fi + + +# Run service +print_log "info" "Running main service..." +exec /dvd diff --git a/main.go b/main.go index 411b48b..1b1ba55 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,31 @@ func listenForMounts() { defer cli.Close() + // Scan all running containers on startup + containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: false}) + if err != nil { + log.Fatal(err) + } + + for _, container := range containers { + info, err := cli.ContainerInspect(ctx, container.ID) + if err != nil { + log.Println(err) + continue + } + + pid := info.State.Pid + version, err := cgroup.GetDeviceCGroupVersion("/", pid) + if err != nil { + log.Println(err) + continue + } + + log.Printf("Checking mounts for process %d\n", pid) + processMounts(container.ID, info.Mounts, pid, version) + } + + // Monitor container start events msgs, errs := cli.Events( ctx, types.EventsOptions{Filters: filters.NewArgs(filters.Arg("event", "start"))}, @@ -96,56 +121,62 @@ func listenForMounts() { } log.Printf("Checking mounts for process %d\n", pid) + processMounts(msg.Actor.ID, info.Mounts, pid, version) + } + } + } +} - for _, mount := range info.Mounts { - log.Printf( - "%s/%v requested a volume mount for %s at %s\n", - msg.Actor.ID, info.State.Pid, mount.Source, mount.Destination, - ) - - if !strings.HasPrefix(mount.Source, "/dev") { - log.Printf("%s is not a device... skipping\n", mount.Source) - continue - } - - api, err := cgroup.New(version) - cgroupPath, sysfsPath, err := api.GetDeviceCGroupMountPath("/", pid) - - if err != nil { - log.Println(err) - break - } - - cgroupPath = path.Join(rootPath, sysfsPath, cgroupPath) - - log.Printf("The cgroup path for process %d is at %v\n", pid, cgroupPath) - - if fileInfo, err := os.Stat(mount.Source); err != nil { - log.Println(err) - continue - } else { - if fileInfo.IsDir() { - err := filepath.Walk(mount.Source, - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } else if info.IsDir() { - return nil - } else if err = applyDeviceRules(api, path, cgroupPath, pid); err != nil { - log.Println(err) - } - return nil - }) - if err != nil { - log.Println(err) - } - } else { - if err = applyDeviceRules(api, mount.Source, cgroupPath, pid); err != nil { - log.Println(err) - } - } - } +func processMounts(containerId string, mounts []types.MountPoint, pid int, version int) { + api, err := cgroup.New(version) + if err != nil { + log.Println(err) + return + } + + for _, mount := range mounts { + log.Printf( + "%s/%v requested a volume mount for %s at %s\n", + containerId, pid, mount.Source, mount.Destination, + ) + + if !strings.HasPrefix(mount.Source, "/dev") { + log.Printf("%s is not a device... skipping\n", mount.Source) + continue + } + + cgroupPath, sysfsPath, err := api.GetDeviceCGroupMountPath("/", pid) + if err != nil { + log.Println(err) + continue + } + cgroupPath = path.Join(rootPath, sysfsPath, cgroupPath) + + log.Printf("The cgroup path for process %d is at %v\n", pid, cgroupPath) + + if fileInfo, err := os.Stat(mount.Source); err != nil { + log.Println(err) + continue + } else { + if fileInfo.IsDir() { + err := filepath.Walk(mount.Source, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } else if info.IsDir() { + return nil + } else if err = applyDeviceRules(api, path, cgroupPath, pid); err != nil { + log.Println(err) + } + return nil + }) + if err != nil { + log.Println(err) + } + } else { + if err = applyDeviceRules(api, mount.Source, cgroupPath, pid); err != nil { + log.Println(err) } } }