diff --git a/.gitignore b/.gitignore index aaadf73..8f0d79d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ go.work.sum # Editor/IDE # .idea/ # .vscode/ +ironmount diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a08692f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.24-alpine3.21 AS builder + +WORKDIR /ironmount + +COPY go.mod ./ + +RUN go mod download + +COPY . . + +ARG TARGETOS=linux +ARG TARGETARCH +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ + go build -o /out/ironmount . + +FROM alpine:3.22 AS runner +WORKDIR / +COPY --from=builder /out/ironmount /ironmount + +ENTRYPOINT ["/ironmount"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9821232 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.8" + +services: + ironmount: + build: + context: . + dockerfile: Dockerfile + container_name: ironmount + restart: unless-stopped + volumes: + - /run/docker/plugins:/run/docker/plugins + - /tmp/ironmount:/tmp/ironmount diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..dba264a --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,3 @@ +package constants + +var VolumeRoot = "/tmp/ironmount" diff --git a/internal/driver/activate.go b/internal/driver/activate.go new file mode 100644 index 0000000..1d6e587 --- /dev/null +++ b/internal/driver/activate.go @@ -0,0 +1,17 @@ +package driver + +import ( + "encoding/json" + "log" + "net/http" +) + +func Activate(w http.ResponseWriter, r *http.Request) { + // Log the activation request + log.Printf("Received activation request: %s", r.URL.Path) + + resp := map[string]any{ + "Implements": []string{"VolumeDriver"}, + } + _ = json.NewEncoder(w).Encode(resp) +} diff --git a/internal/driver/create.go b/internal/driver/create.go new file mode 100644 index 0000000..53007f2 --- /dev/null +++ b/internal/driver/create.go @@ -0,0 +1,25 @@ +package driver + +import ( + "encoding/json" + "ironmount/internal/constants" + "net/http" + "os" + "path/filepath" +) + +func Create(w http.ResponseWriter, r *http.Request) { + var req struct { + Name string + } + _ = json.NewDecoder(r.Body).Decode(&req) + + volPath := filepath.Join(constants.VolumeRoot, req.Name) + if err := os.MkdirAll(volPath, 0755); err != nil { + _ = json.NewEncoder(w).Encode(map[string]string{"Err": err.Error()}) + return + } + + volumes[req.Name] = Volume{Name: req.Name, Path: volPath} + _ = json.NewEncoder(w).Encode(map[string]string{"Err": ""}) +} diff --git a/internal/driver/mount.go b/internal/driver/mount.go new file mode 100644 index 0000000..de772f6 --- /dev/null +++ b/internal/driver/mount.go @@ -0,0 +1,27 @@ +package driver + +import ( + "encoding/json" + "net/http" +) + +func Mount(w http.ResponseWriter, r *http.Request) { + var req MountRequest + err := json.NewDecoder(r.Body).Decode(&req) + + if err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + vol, ok := volumes[req.Name] + if !ok { + _ = json.NewEncoder(w).Encode(map[string]string{"Err": "volume not found"}) + return + } + + _ = json.NewEncoder(w).Encode(map[string]string{ + "Mountpoint": vol.Path, + "Err": "", + }) +} diff --git a/internal/driver/path.go b/internal/driver/path.go new file mode 100644 index 0000000..90e6c35 --- /dev/null +++ b/internal/driver/path.go @@ -0,0 +1,22 @@ +package driver + +import ( + "encoding/json" + "net/http" +) + +func Path(w http.ResponseWriter, r *http.Request) { + var req PathRequest + _ = json.NewDecoder(r.Body).Decode(&req) + + vol, ok := volumes[req.Name] + if !ok { + _ = json.NewEncoder(w).Encode(map[string]string{"Err": "volume not found"}) + return + } + + _ = json.NewEncoder(w).Encode(map[string]string{ + "Mountpoint": vol.Path, + "Err": "", + }) +} diff --git a/internal/driver/remove.go b/internal/driver/remove.go new file mode 100644 index 0000000..4cfbffd --- /dev/null +++ b/internal/driver/remove.go @@ -0,0 +1,14 @@ +package driver + +import ( + "encoding/json" + "net/http" +) + +func Remove(w http.ResponseWriter, r *http.Request) { + var req RemoveRequest + _ = json.NewDecoder(r.Body).Decode(&req) + + delete(volumes, req.Name) + _ = json.NewEncoder(w).Encode(map[string]string{"Err": ""}) +} diff --git a/internal/driver/state.go b/internal/driver/state.go new file mode 100644 index 0000000..a07cc5d --- /dev/null +++ b/internal/driver/state.go @@ -0,0 +1,8 @@ +package driver + +type Volume struct { + Name string + Path string +} + +var volumes = map[string]Volume{} diff --git a/internal/driver/type.go b/internal/driver/type.go new file mode 100644 index 0000000..6697c93 --- /dev/null +++ b/internal/driver/type.go @@ -0,0 +1,22 @@ +package driver + +// CreateRequest is the JSON request for Create +type CreateRequest struct { + Name string +} + +// RemoveRequest is the JSON request for Remove +type RemoveRequest struct { + Name string +} + +// MountRequest is the JSON request for Mount +type MountRequest struct { + Name string + ID string +} + +// PathRequest is the JSON request for Path +type PathRequest struct { + Name string +} diff --git a/internal/driver/unmount.go b/internal/driver/unmount.go new file mode 100644 index 0000000..0eb029f --- /dev/null +++ b/internal/driver/unmount.go @@ -0,0 +1,10 @@ +package driver + +import ( + "encoding/json" + "net/http" +) + +func Unmount(w http.ResponseWriter, r *http.Request) { + _ = json.NewEncoder(w).Encode(map[string]string{"Err": ""}) +} diff --git a/main.go b/main.go index e54950a..97d537b 100644 --- a/main.go +++ b/main.go @@ -1,20 +1,52 @@ package main import ( - "fmt" + "ironmount/internal/driver" + "log" + "net" "net/http" + "os" ) -func handler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, World!") +const volumeRoot = "/tmp/ironmount" + +type Volume struct { + Name string + Path string } +var volumes = map[string]Volume{} + func main() { - http.HandleFunc("/", handler) - - fmt.Println("Server is running on http://localhost:8080") - err := http.ListenAndServe(":8080", nil) - if err != nil { - fmt.Println("Error starting server:", err) + if err := os.MkdirAll("/run/docker/plugins", 0755); err != nil { + log.Fatalf("Failed to create plugin directory: %v", err) } + + if err := os.MkdirAll(volumeRoot, 0755); err != nil { + log.Fatalf("Failed to create volume root: %v", err) + } + + if err := os.MkdirAll("/run/docker/plugins", 0755); err != nil { + log.Fatalf("Failed to create plugin directory: %v", err) + } + + socketPath := "/run/docker/plugins/ironmount.sock" + if err := os.RemoveAll(socketPath); err != nil { + log.Fatalf("Failed to remove existing socket: %v", err) + } + + http.HandleFunc("/Plugin.Activate", driver.Activate) + http.HandleFunc("/VolumeDriver.Create", driver.Create) + http.HandleFunc("/VolumeDriver.Remove", driver.Remove) + http.HandleFunc("/VolumeDriver.Mount", driver.Mount) + http.HandleFunc("/VolumeDriver.Unmount", driver.Unmount) + http.HandleFunc("/VolumeDriver.Path", driver.Path) + + listener, err := net.Listen("unix", socketPath) + if err != nil { + log.Fatalf("Failed to listen on socket: %v", err) + } + + log.Printf("Irounmount plugin started, listening on %s", socketPath) + log.Fatal(http.Serve(listener, nil)) }