diff --git a/.gitignore b/.gitignore index 5913ccc..fcdf5d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,38 @@ -*~ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib docker-volume-moosefs -*.rpm + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Plugin build artifacts +plugin/rootfs/ *.deb -obj -.idea -vendor/*/ +*.rpm +obj/ + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + diff --git a/Makefile b/Makefile index 9c684fb..1a1a022 100644 --- a/Makefile +++ b/Makefile @@ -44,4 +44,19 @@ deb: compile clean: rm -fr obj *.deb *.rpm docker-volume-moosefs -.PHONY: clean rpm-deps deb-deps fmt deps compile +plugin: compile + mkdir -p plugin/rootfs + docker build -t moosefs-plugin-build -f plugin/Dockerfile . + docker create --name tmp moosefs-plugin-build + docker export tmp | tar -x -C plugin/rootfs + docker rm -vf tmp + docker rmi moosefs-plugin-build + +plugin-enable: + docker plugin create moosefs/docker-volume-moosefs:$(VERSION) plugin + docker plugin enable moosefs/docker-volume-moosefs:$(VERSION) + +plugin-push: plugin plugin-enable + docker plugin push moosefs/docker-volume-moosefs:$(VERSION) + +.PHONY: clean rpm-deps deb-deps fmt deps compile plugin plugin-enable plugin-push diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..4f106ca --- /dev/null +++ b/build.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Version +VERSION="0.2.0" +PLUGIN_NAME="moosefs/docker-volume-moosefs:${VERSION}" + +# Function to print step +print_step() { + echo -e "${YELLOW}[*] $1${NC}" +} + +# Function to print success +print_success() { + echo -e "${GREEN}[+] $1${NC}" +} + +# Function to print error and exit +print_error() { + echo -e "${RED}[-] $1${NC}" + exit 1 +} + +# Clean previous build +print_step "Cleaning previous build..." +rm -rf plugin/rootfs *.deb *.rpm docker-volume-moosefs obj +print_success "Clean completed" + +# Clean existing plugin if it exists +print_step "Checking for existing plugin..." +if docker plugin inspect ${PLUGIN_NAME} >/dev/null 2>&1; then + print_step "Disabling existing plugin..." + docker plugin disable ${PLUGIN_NAME} >/dev/null 2>&1 + print_step "Removing existing plugin..." + docker plugin rm ${PLUGIN_NAME} >/dev/null 2>&1 +fi +print_success "Plugin cleanup completed" + +# Build Go binary +print_step "Building Go binary..." +go build || print_error "Go build failed" +print_success "Binary built successfully" + +# Create plugin +print_step "Creating Docker plugin..." +mkdir -p plugin/rootfs || print_error "Failed to create plugin directory" + +# Build Docker image +print_step "Building Docker image..." +docker build -t moosefs-plugin-build -f plugin/Dockerfile . || print_error "Docker build failed" + +print_step "Creating temporary container..." +docker create --name tmp moosefs-plugin-build || print_error "Failed to create temporary container" + +print_step "Extracting rootfs..." +docker export tmp | tar -x -C plugin/rootfs || print_error "Failed to extract rootfs" + +print_step "Cleaning up temporary container..." +docker rm -vf tmp +docker rmi moosefs-plugin-build + +# Create and enable plugin +print_step "Creating Docker plugin..." +docker plugin create ${PLUGIN_NAME} plugin || print_error "Failed to create plugin" + +print_step "Enabling Docker plugin..." +docker plugin enable ${PLUGIN_NAME} || print_error "Failed to enable plugin" + +print_success "Build completed successfully!" +echo -e "${GREEN}Plugin ${PLUGIN_NAME} is now ready to use${NC}" +echo -e "${YELLOW}You can create a volume with:${NC}" +echo -e "docker volume create -d ${PLUGIN_NAME} --name test_volume" diff --git a/driver.go b/driver.go index 6450750..78ec575 100644 --- a/driver.go +++ b/driver.go @@ -3,22 +3,29 @@ package main import ( "errors" "fmt" - "os" - "path" + "os" + "strings" "path/filepath" "sync" - "syscall" - log "github.com/Sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/davecgh/go-spew/spew" "github.com/docker/go-plugins-helpers/volume" ) +// Configuration structure +type MoosefsConfig struct { + MasterHost string + MasterPort string + RootDir string + MountOptions []string +} + // A single volume instance type moosefsMount struct { name string - path string + path string root string } @@ -27,16 +34,21 @@ type moosefsDriver struct { m *sync.Mutex } -func newMooseFSDriver(root string) moosefsDriver { - d := moosefsDriver{ +func newMooseFSDriver(root string) (*moosefsDriver, error) { + d := &moosefsDriver{ mounts: make(map[string]*moosefsMount), m: &sync.Mutex{}, } - return d + + if err := d.loadState(); err != nil { + return nil, err + } + + return d, nil } func (d moosefsDriver) Create(r *volume.CreateRequest) error { - var volumeRoot string + var volumeRoot string d.m.Lock() defer d.m.Unlock() @@ -45,15 +57,15 @@ func (d moosefsDriver) Create(r *volume.CreateRequest) error { volumeRoot = optsRoot } else { // Assume the default root - volumeRoot = *root + volumeRoot = *root } - volumePath := filepath.Join(volumeRoot, r.Name) + volumePath := filepath.Join(volumeRoot, r.Name) - if err := mkdir(volumePath); err != nil { + if err := mkdir(volumePath); err != nil { return err } - + if !ismoosefs(volumePath) { emsg := fmt.Sprintf("Cannot create volume %s as it's not a valid MooseFS mount", volumePath) log.Error(emsg) @@ -66,12 +78,9 @@ func (d moosefsDriver) Create(r *volume.CreateRequest) error { return errors.New(emsg) } - if err := mkdir(volumePath); err != nil { - return err - } d.mounts[r.Name] = &moosefsMount{ name: r.Name, - path: volumePath, + path: volumePath, root: volumeRoot, } @@ -79,6 +88,12 @@ func (d moosefsDriver) Create(r *volume.CreateRequest) error { spew.Dump(d.mounts) } + if err := d.saveState(); err != nil { + // If we can't save state, remove the volume from memory + delete(d.mounts, r.Name) + return err + } + return nil } @@ -87,6 +102,9 @@ func (d moosefsDriver) Remove(r *volume.RemoveRequest) error { defer d.m.Unlock() if _, ok := d.mounts[r.Name]; ok { delete(d.mounts, r.Name) + if err := d.saveState(); err != nil { + return err + } } return nil } @@ -99,16 +117,16 @@ func (d moosefsDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) } func (d moosefsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) { - volumePath := filepath.Join(d.mounts[r.Name].root, r.Name) - if !ismoosefs(volumePath) { - emsg := fmt.Sprintf("Cannot mount volume %s as it's not a valid MooseFS mount", volumePath) - log.Error(emsg) - return &volume.MountResponse{}, errors.New(emsg) - } - if _, ok := d.mounts[r.Name]; ok { - return &volume.MountResponse{Mountpoint: d.mounts[r.Name].path}, nil - } - return &volume.MountResponse{}, nil + volumePath := filepath.Join(d.mounts[r.Name].root, r.Name) + if !ismoosefs(volumePath) { + emsg := fmt.Sprintf("Cannot mount volume %s as it's not a valid MooseFS mount", volumePath) + log.Error(emsg) + return &volume.MountResponse{}, errors.New(emsg) + } + if _, ok := d.mounts[r.Name]; ok { + return &volume.MountResponse{Mountpoint: d.mounts[r.Name].path}, nil + } + return &volume.MountResponse{}, nil } func (d moosefsDriver) Unmount(r *volume.UnmountRequest) error { @@ -137,15 +155,41 @@ func (d moosefsDriver) Capabilities() *volume.CapabilitiesResponse { return &res } -// Check if MooseFS is mounted in mountpoint using the .masterinfo file -func ismoosefs(mountpoint string) bool { - stat := syscall.Statfs_t{} - err := syscall.Statfs(path.Join(mountpoint, ".masterinfo"), &stat) - if err != nil { - log.Errorf("Could not determine filesystem type for %s: %s", mountpoint, err) - return false - } - return true +// Check if path is under a MooseFS mount +func ismoosefs(checkPath string) bool { + absPath, err := filepath.Abs(checkPath) + if err != nil { + log.Errorf("Cannot get absolute path for %s: %s", checkPath, err) + return false + } + + data, err := os.ReadFile("/proc/mounts") + if err != nil { + log.Errorf("Cannot read /proc/mounts: %s", err) + return false + } + + mounts := strings.Split(string(data), "\n") + for _, mount := range mounts { + fields := strings.Fields(mount) + if len(fields) < 3 { + continue + } + + mountPoint := fields[1] + fsType := fields[2] + + if fsType == "fuse.mfs" { + log.Infof("Found MooseFS mount at %s", mountPoint) + if strings.HasPrefix(absPath, mountPoint) { + log.Infof("Path %s is under MooseFS mount %s", absPath, mountPoint) + return true + } + } + } + + log.Infof("Path %s is not under any MooseFS mount", absPath) + return false } func mkdir(path string) error { diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c6fda27 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/liberodark/docker-volume-moosefs + +go 1.21 + +toolchain go1.23.3 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651 + github.com/sirupsen/logrus v1.9.3 +) + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect + github.com/docker/go-connections v0.5.0 // indirect + golang.org/x/sys v0.10.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9efb722 --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651 h1:YcvzLmdrP/b8kLAGJ8GT7bdncgCAiWxJZIlt84D+RJg= +github.com/docker/go-plugins-helpers v0.0.0-20211224144127-6eecb7beb651/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 53a9ac0..aa429da 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "flag" "fmt" - log "github.com/Sirupsen/logrus" + log "github.com/sirupsen/logrus" "os/user" "strconv" @@ -29,7 +29,10 @@ func main() { u, _ := user.Lookup("root") gid, _ := strconv.Atoi(u.Gid) - d := newMooseFSDriver(*root) + d, err := newMooseFSDriver(*root) + if err != nil { + log.Fatalf("Failed to initialize driver: %v", err) + } h := volume.NewHandler(d) fmt.Println(h.ServeUnix("moosefs", gid)) } diff --git a/persistence.go b/persistence.go new file mode 100644 index 0000000..eecb867 --- /dev/null +++ b/persistence.go @@ -0,0 +1,61 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" +) + +const stateFile = "/var/lib/docker-volume-moosefs/state.json" + +type State struct { + Mounts map[string]*moosefsMount `json:"mounts"` +} + +func ensureStateDir() error { + dir := filepath.Dir(stateFile) + return os.MkdirAll(dir, 0755) +} + +func (d *moosefsDriver) saveState() error { + d.m.Lock() + defer d.m.Unlock() + + if err := ensureStateDir(); err != nil { + return err + } + + state := State{ + Mounts: d.mounts, + } + + data, err := json.Marshal(state) + if err != nil { + return err + } + + return ioutil.WriteFile(stateFile, data, 0644) +} + +func (d *moosefsDriver) loadState() error { + d.m.Lock() + defer d.m.Unlock() + + data, err := ioutil.ReadFile(stateFile) + if os.IsNotExist(err) { + // No state file exists yet, start with empty state + return nil + } + if err != nil { + return err + } + + var state State + if err := json.Unmarshal(data, &state); err != nil { + return err + } + + d.mounts = state.Mounts + return nil +} diff --git a/plugin/Dockerfile b/plugin/Dockerfile new file mode 100644 index 0000000..10ee089 --- /dev/null +++ b/plugin/Dockerfile @@ -0,0 +1,31 @@ +# Build stage +FROM golang:1.20-alpine AS builder +WORKDIR /go/src/github.com/liberodark/docker-volume-moosefs +COPY . . +# Recréer un nouveau go.mod dans le container +RUN rm -f go.mod go.sum && \ + go mod init github.com/liberodark/docker-volume-moosefs && \ + go get github.com/sirupsen/logrus@v1.9.3 && \ + go get github.com/docker/go-plugins-helpers@v0.0.0-20211224144127-6eecb7beb651 && \ + go get github.com/davecgh/go-spew@v1.1.1 && \ + go mod tidy && \ + CGO_ENABLED=0 go build -o /go/bin/docker-volume-moosefs + +# Final stage +FROM ubuntu:22.04 +ENV DEBIAN_FRONTEND=noninteractive + +# Installation du client MooseFS +RUN apt-get update && \ + apt-get install -y wget gnupg2 && \ + mkdir -p /etc/apt/keyrings && \ + wget -O - http://repository.moosefs.com/moosefs.key | gpg --dearmor > /etc/apt/keyrings/moosefs.gpg && \ + echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/moosefs.gpg] http://repository.moosefs.com/moosefs-4/apt/ubuntu/jammy jammy main" > /etc/apt/sources.list.d/moosefs.list && \ + apt-get update && \ + apt-get install -y moosefs-client && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=builder /go/bin/docker-volume-moosefs /usr/bin/docker-volume-moosefs +RUN mkdir -p /run/docker/plugins /var/lib/docker-volume-moosefs +CMD ["docker-volume-moosefs"] diff --git a/plugin/config.json b/plugin/config.json new file mode 100644 index 0000000..e63c7d2 --- /dev/null +++ b/plugin/config.json @@ -0,0 +1,25 @@ +{ + "description": "MooseFS plugin for Docker", + "documentation": "https://github.com/liberodark/docker-volume-moosefs", + "entrypoint": ["/usr/bin/docker-volume-moosefs"], + "interface": { + "types": ["docker.volumedriver/1.0"], + "socket": "moosefs.sock" + }, + "linux": { + "capabilities": ["CAP_SYS_ADMIN"], + "mounts": [ + { + "type": "bind", + "source": "/mnt", + "destination": "/mnt", + "options": ["rbind"] + } + ] + }, + "propagatedMount": "/mnt", + "workdir": "/", + "network": { + "type": "host" + } +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..54fee8f --- /dev/null +++ b/shell.nix @@ -0,0 +1,17 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + go + git + gcc + docker + moosefs + ]; + + shellHook = '' + echo "MooseFS Docker Volume Plugin development environment" + echo "Go version: $(go version)" + echo "Docker version: $(docker --version)" + ''; +}