feat(volume-browser): phase 1 - path resolver & file system API
- Extract volume path resolution into shared internal/volume/resolver.go - File browser operations: ListDir, OpenFile, WriteZip, SaveFile - Strict path traversal protection (double-validated) - API endpoints: browse, download (file or zip), upload (multipart) - Refactor deployer to use shared resolver
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
"github.com/alexei/docker-watcher/internal/notify"
|
||||
"github.com/alexei/docker-watcher/internal/npm"
|
||||
"github.com/alexei/docker-watcher/internal/store"
|
||||
"github.com/alexei/docker-watcher/internal/volume"
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -619,13 +619,7 @@ func (d *Deployer) mergeEnvVars(project store.Project, stageID string) []string
|
||||
}
|
||||
|
||||
// computeVolumeMounts builds Docker mount specifications from the project's volume config.
|
||||
// Resolves the host path based on the volume's scope:
|
||||
// - instance: {base}/{project}/{stage}-{tag}/{source}
|
||||
// - stage: {base}/{project}/{stage}/{source}
|
||||
// - project: {base}/{project}/{source}
|
||||
// - project_named: {base}/{project}/_named/{name}/{source}
|
||||
// - named: {base}/_named/{name}/{source}
|
||||
// - ephemeral: tmpfs mount (no host path)
|
||||
// Uses the shared volume.ResolvePath for path resolution.
|
||||
func (d *Deployer) computeVolumeMounts(projectID, projectName, stageName, imageTag, basePath string) []mount.Mount {
|
||||
vols, err := d.store.GetVolumesByProjectID(projectID)
|
||||
if err != nil {
|
||||
@@ -637,9 +631,15 @@ func (d *Deployer) computeVolumeMounts(projectID, projectName, stageName, imageT
|
||||
return nil
|
||||
}
|
||||
|
||||
params := volume.ResolveParams{
|
||||
BasePath: basePath,
|
||||
ProjectName: projectName,
|
||||
StageName: stageName,
|
||||
ImageTag: imageTag,
|
||||
}
|
||||
|
||||
mounts := make([]mount.Mount, 0, len(vols))
|
||||
for _, vol := range vols {
|
||||
// Resolve scope — use Scope field, fall back to Mode for backward compat.
|
||||
scope := vol.Scope
|
||||
if scope == "" {
|
||||
switch vol.Mode {
|
||||
@@ -659,22 +659,10 @@ func (d *Deployer) computeVolumeMounts(projectID, projectName, stageName, imageT
|
||||
continue
|
||||
}
|
||||
|
||||
// Build host path based on scope.
|
||||
var source string
|
||||
switch scope {
|
||||
case "instance":
|
||||
source = filepath.Join(basePath, projectName, fmt.Sprintf("%s-%s", stageName, imageTag), vol.Source)
|
||||
case "stage":
|
||||
source = filepath.Join(basePath, projectName, stageName, vol.Source)
|
||||
case "project":
|
||||
source = filepath.Join(basePath, projectName, vol.Source)
|
||||
case "project_named":
|
||||
source = filepath.Join(basePath, projectName, "_named", vol.Name, vol.Source)
|
||||
case "named":
|
||||
source = filepath.Join(basePath, "_named", vol.Name, vol.Source)
|
||||
default:
|
||||
// Fallback: treat as project scope.
|
||||
source = filepath.Join(basePath, projectName, vol.Source)
|
||||
source, err := volume.ResolvePath(vol, params)
|
||||
if err != nil {
|
||||
slog.Warn("resolve volume path", "volume_id", vol.ID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
mounts = append(mounts, mount.Mount{
|
||||
|
||||
Reference in New Issue
Block a user