From d2a2e22bbed6c1200bb1abf9978e69361eafd999 Mon Sep 17 00:00:00 2001
From: duanhongyi <duanhongyi@doopai.com>
Date: Wed, 3 Dec 2025 13:21:57 +0800
Subject: [PATCH] feat(volumes): add volumes serve

---
 go.mod                                        |   2 +-
 go.sum                                        |   4 +-
 internal/commands/volumes.go                  | 219 +++---------------
 internal/commands/volumes_test.go             |  92 +++-----
 internal/parser/volumes.go                    |  60 ++---
 .../drycc/en_US/LC_MESSAGES/cli.po            |  26 +--
 pkg/i18n/translations/drycc/template.pot      |  26 +--
 .../drycc/zh_CN/LC_MESSAGES/cli.po            |  28 +--
 8 files changed, 94 insertions(+), 363 deletions(-)

diff --git a/go.mod b/go.mod
index 5633be42..9b150614 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.25
 require (
 	github.com/chai2010/gettext-go v1.0.3
 	github.com/containerd/console v1.0.4
-	github.com/drycc/controller-sdk-go v0.0.0-20250930075415-578495a55414
+	github.com/drycc/controller-sdk-go v0.0.0-20251203071902-cc535b118251
 	github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf
 	github.com/minio/selfupdate v0.6.0
 	github.com/olekukonko/tablewriter v0.0.5
diff --git a/go.sum b/go.sum
index 116f7b3c..5c6124b0 100644
--- a/go.sum
+++ b/go.sum
@@ -11,8 +11,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/drycc/controller-sdk-go v0.0.0-20250930075415-578495a55414 h1:wiVKgwQsSEBbrQj92Lor6YknaAwPUVp1s/VmAmS3/lI=
-github.com/drycc/controller-sdk-go v0.0.0-20250930075415-578495a55414/go.mod h1:eHcmYwg81ASlP55/U587xnBZnZoeZnPHXGeQ8nYWnsg=
+github.com/drycc/controller-sdk-go v0.0.0-20251203071902-cc535b118251 h1:6bhcFnQn478sys8IXaeZW4OnurK64gTNMsu2NqKoCIw=
+github.com/drycc/controller-sdk-go v0.0.0-20251203071902-cc535b118251/go.mod h1:eHcmYwg81ASlP55/U587xnBZnZoeZnPHXGeQ8nYWnsg=
 github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf h1:CYy3NoPhfFhkGAbEppTOQfY/HC2s0FJDcBgbtRKeweg=
 github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf/go.mod h1:BrrNrNskHKm+nJYhXfGuI114w8nupi0AMo8QZHID7CM=
 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
diff --git a/internal/commands/volumes.go b/internal/commands/volumes.go
index 0fa5e20b..d00e39b8 100644
--- a/internal/commands/volumes.go
+++ b/internal/commands/volumes.go
@@ -1,17 +1,16 @@
 package commands
 
 import (
+	"context"
 	"fmt"
-	"io"
-	"net/http"
 	"net/url"
 	"os"
-	"path"
+	"os/signal"
 	"regexp"
 	"strings"
+	"syscall"
 	"time"
 
-	drycc "github.com/drycc/controller-sdk-go"
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/controller-sdk-go/volumes"
 	"github.com/drycc/workflow-cli/internal/loader"
@@ -161,17 +160,41 @@ func (d *DryccCmd) VolumesDelete(appID, name string) error {
 	return nil
 }
 
-// VolumesClient a client for manage volume file
-func (d *DryccCmd) VolumesClient(appID, cmd string, args ...string) error {
-	switch cmd {
-	case "ls":
-		return d.volumesClientLs(appID, args[0])
-	case "cp":
-		return d.volumesClientCp(appID, args[0], args[1])
-	case "rm":
-		return d.volumesClientRm(appID, args[0])
-	default:
-		return fmt.Errorf("unknown command %s", cmd)
+// VolumesServe Serve serves an app's volume.
+func (d *DryccCmd) VolumesServe(appID, name string) error {
+	appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID)
+	if err != nil {
+		return err
+	}
+	parent, cancel := context.WithCancel(context.Background())
+	defer cancel()
+	quit := progress(d.WOut)
+	ctx, filer, err := volumes.Serve(parent, s.Client, appID, name)
+	quit <- true
+	<-quit
+	if err != nil {
+		return err
+	}
+
+	table := d.getDefaultFormatTable([]string{})
+	table.Append([]string{"Endpoint:", filer["endpoint"]})
+	table.Append([]string{"Username:", filer["username"]})
+	table.Append([]string{"Password:", filer["password"]})
+	table.Render()
+	d.Print("\n")
+
+	signalChan := make(chan os.Signal, 1)
+	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
+	d.Printf("WebDAV service for volume %s is running. Press Ctrl+C to stop.\n", name)
+	for {
+		select {
+		case <-signalChan:
+			return nil
+		case <-ctx.Done():
+			return nil
+		default:
+			time.Sleep(2 * time.Second)
+		}
 	}
 }
 
@@ -233,172 +256,6 @@ func (d *DryccCmd) VolumesUnmount(appID string, name string, volumeVars []string
 	return nil
 }
 
-// volumesClientLs get all directory entries sorted by filename.
-func (d *DryccCmd) volumesClientLs(appID, vol string) error {
-	appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID)
-	if err != nil {
-		return err
-	}
-
-	name, path, err := parseVol(vol)
-	if err != nil {
-		return err
-	}
-	dirs, _, err := volumes.ListDir(s.Client, appID, name, path, 3000)
-	if err != nil {
-		return err
-	}
-
-	table := d.getDefaultFormatTable([]string{})
-	for _, dir := range dirs {
-		if dir.Type == "dir" {
-			dir.Name = fmt.Sprintf("%s/", dir.Name)
-		}
-		table.Append([]string{fmt.Sprintf("[%s]", d.formatTime(dir.Timestamp)), dir.Size, dir.Name})
-	}
-	table.Render()
-	return nil
-}
-
-func (d *DryccCmd) volumesClientGetAll(client *drycc.Client, appID, volumeID, volumePath, localPath string) error {
-	if _, err := os.Stat(localPath); err != nil && os.IsNotExist(err) {
-		os.MkdirAll(localPath, os.ModePerm)
-	}
-	dirs, _, err := volumes.ListDir(client, appID, volumeID, volumePath, 3000)
-	if err != nil {
-		return err
-	}
-	for _, dir := range dirs {
-		_, subpath := path.Split(dir.Path)
-		filepath := path.Join(localPath, subpath)
-		if dir.Type == "file" {
-			res, err := volumes.GetFile(client, appID, volumeID, dir.Path)
-			if err != nil {
-				return err
-			}
-			w, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
-			if err != nil {
-				return err
-			}
-			bar := d.newProgressbar(res.ContentLength, "↓", filepath)
-			defer w.Close()
-			if _, err = io.Copy(io.MultiWriter(w, bar), res.Body); err != nil {
-				return err
-			}
-		} else {
-			os.MkdirAll(filepath, os.ModePerm)
-			if err := d.volumesClientGetAll(client, appID, volumeID, dir.Path, filepath); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (d *DryccCmd) volumesClientPostAll(client *drycc.Client, appID, volumeID, volumePath, localPath string) error {
-	if file, err := os.Stat(localPath); err != nil {
-		return err
-	} else if !file.IsDir() {
-		file, err := os.Open(localPath)
-		if err != nil {
-			return err
-		}
-		defer file.Close()
-
-		stat, err := file.Stat()
-		if err != nil {
-			return err
-		}
-		if stat.Size() > 0 { // ignore empty file
-			reader := progressbar.NewReader(file, d.newProgressbar(stat.Size(), "↑", localPath))
-			if _, err := volumes.PostFile(client, appID, volumeID, volumePath, file.Name(), stat.Size(), &reader); err != nil {
-				return err
-			}
-		} else {
-			d.newProgressbar(1, "?", localPath).Finish()
-		}
-		return nil
-	}
-	if entries, err := os.ReadDir(localPath); err == nil {
-		for _, entry := range entries {
-			var dstFilepath string
-			if entry.IsDir() {
-				dstFilepath = path.Join(volumePath, entry.Name())
-			} else {
-				dstFilepath = volumePath
-			}
-			if err := d.volumesClientPostAll(client, appID, volumeID, dstFilepath, path.Join(localPath, entry.Name())); err != nil {
-				return err
-			}
-		}
-	} else {
-		return err
-	}
-	return nil
-}
-
-// volumesClientCp copy files between volume and local file
-func (d *DryccCmd) volumesClientCp(appID, src, dst string) error {
-	appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID)
-	if err != nil {
-		return err
-	}
-	if strings.HasPrefix(src, "vol://") {
-		f, err := os.Stat(dst)
-		if err != nil {
-			return err
-		}
-		if !f.IsDir() {
-			return fmt.Errorf("the local path must be an existing dir")
-		}
-		volumeID, volumePath, err := parseVol(src)
-		if err != nil {
-			return err
-		}
-		if dirs, _, err := volumes.ListDir(s.Client, appID, volumeID, volumePath, 3000); err == nil && (len(dirs) != 1 || dirs[0].Type != "file") {
-			dst = mergeDestDir(dst, volumePath)
-		}
-		return d.volumesClientGetAll(s.Client, appID, volumeID, volumePath, dst)
-	} else if strings.HasPrefix(dst, "vol://") {
-		volumeID, volumePath, err := parseVol(dst)
-		if err != nil {
-			return err
-		}
-		if dirs, _, err := volumes.ListDir(s.Client, appID, volumeID, volumePath, 3000); err == nil {
-			names := strings.Split(strings.Trim(src, "/"), "/")
-			if len(dirs) == 1 && dirs[0].Type == "file" && strings.HasSuffix(strings.Trim(volumePath, "/"), names[len(names)-1]) {
-				return fmt.Errorf("the volume path cannot be an existing file")
-			}
-		}
-		if file, err := os.Stat(src); err == nil && file.IsDir() {
-			volumePath = mergeDestDir(volumePath, src)
-		}
-		return d.volumesClientPostAll(s.Client, appID, volumeID, volumePath, src)
-	}
-	return nil
-}
-
-// volumesClientRm delete a file from volume
-func (d *DryccCmd) volumesClientRm(appID, vol string) error {
-	appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID)
-	if err != nil {
-		return err
-	}
-	host, path, err := parseVol(vol)
-	if err != nil {
-		return err
-	}
-	res, err := volumes.DeleteFile(s.Client, appID, host, path)
-	if err != nil {
-		return err
-	}
-	if res.StatusCode != http.StatusOK {
-		return fmt.Errorf("incorrect http status code %d", res.StatusCode)
-	}
-
-	return nil
-}
-
 func parseVolume(volumeVars []string) (map[string]any, error) {
 	volumeMap := make(map[string]any)
 	regex := regexp.MustCompile(`^([a-z0-9]+(?:-[a-z0-9]+)*)=(\/([\w]+[\w-]*\/?)+)$`)
diff --git a/internal/commands/volumes_test.go b/internal/commands/volumes_test.go
index cb850604..30c29c3d 100644
--- a/internal/commands/volumes_test.go
+++ b/internal/commands/volumes_test.go
@@ -4,8 +4,9 @@ import (
 	"bytes"
 	"fmt"
 	"net/http"
-	"os"
+	"syscall"
 	"testing"
+	"time"
 
 	"github.com/drycc/controller-sdk-go/api"
 	"github.com/drycc/workflow-cli/pkg/testutil"
@@ -134,7 +135,7 @@ Updated:       2020-08-26T00:00:00UTC
 `)
 }
 
-func TestVolumesClientLs(t *testing.T) {
+func TestVolumesServe(t *testing.T) {
 	t.Parallel()
 	cf, server, err := testutil.NewTestServerAndClient()
 	if err != nil {
@@ -143,68 +144,43 @@ func TestVolumesClientLs(t *testing.T) {
 	defer server.Close()
 	var b bytes.Buffer
 	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
-	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/client/", func(w http.ResponseWriter, _ *http.Request) {
+	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/filer/_/ping", func(w http.ResponseWriter, _ *http.Request) {
 		testutil.SetHeaders(w)
-		fmt.Fprintf(w, `{"results": [
-  {"name":"handler.go","size":"4159","timestamp":"2024-06-25T22:55:16+08:00","type":"file","path":"/handler.go"},
-  {"name":"handler_test.go","size":"2310","timestamp":"2024-06-04T15:29:45+08:00","type":"file","path":"/handler_test.go"}
-], "count": 2}`)
+		fmt.Fprintf(w, `pong`)
 	})
-
-	err = cmdr.VolumesClient("example-go", "ls", "vol://myvolume")
-	assert.NoError(t, err)
-	assert.Contains(t, b.String(), "handler_test.go")
-}
-
-func TestVolumesClientCp(t *testing.T) {
-	t.Parallel()
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
-	}
-	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/client/", func(w http.ResponseWriter, r *http.Request) {
-		testutil.SetHeaders(w)
-		if r.URL.RawQuery == "path=etc" {
-			fmt.Fprintf(w, `{"results":[],"count":0}`)
-		} else if r.Method == http.MethodGet {
-			fmt.Fprintf(w, `{"results":[{"name":"hello.txt","size":"4159","timestamp":"2024-06-25T22:55:16+08:00","type":"file","path":"/hello.txt"}], "count": 1}`)
-		}
-	})
-	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/client/hello.txt", func(w http.ResponseWriter, _ *http.Request) {
+	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/filer/_/bind", func(w http.ResponseWriter, _ *http.Request) {
 		testutil.SetHeaders(w)
-		fmt.Fprintf(w, `hello word`)
+		fmt.Fprintf(w, `{"endpoint": "/v2/apps/example-go/volumes/myvolume/filer/webdav", "username": "user", "password": "pass"}`)
 	})
-	defer server.Close()
 
-	var b bytes.Buffer
-	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
-	// test download file
-	err = cmdr.VolumesClient("example-go", "cp", "vol://myvolume/hello.txt", "/tmp")
-	assert.NoError(t, err)
-	result, err := os.ReadFile("/tmp/hello.txt")
-	assert.NoError(t, err)
-	assert.Equal(t, string(result), `hello word`, "output")
-	// test upload file
-	err = cmdr.VolumesClient("example-go", "cp", "/tmp/hello.txt", "vol://myvolume/etc")
-	assert.NoError(t, err)
-}
-
-func TestVolumesClientRm(t *testing.T) {
-	t.Parallel()
-	cf, server, err := testutil.NewTestServerAndClient()
-	if err != nil {
-		t.Fatal(err)
+	// Use a channel to signal when the method has been called
+	done := make(chan error, 1)
+	go func() {
+		done <- cmdr.VolumesServe("example-go", "myvolume")
+	}()
+
+	// Give the goroutine time to start and produce output
+	time.Sleep(500 * time.Millisecond)
+
+	// Send interrupt signal to stop the blocking method
+	go func() {
+		time.Sleep(100 * time.Millisecond)
+		syscall.Kill(syscall.Getpid(), syscall.SIGINT)
+	}()
+
+	// Wait for completion with timeout
+	select {
+	case err := <-done:
+		assert.NoError(t, err)
+	case <-time.After(5 * time.Second):
+		t.Fatal("VolumesServe did not complete within timeout")
 	}
-	defer server.Close()
-	var b bytes.Buffer
-	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
-	// test rm file
-	server.Mux.HandleFunc("/v2/apps/example-go/volumes/myvolume/client/etc/hello.txt", func(w http.ResponseWriter, _ *http.Request) {
-		testutil.SetHeaders(w)
-		w.WriteHeader(http.StatusOK)
-	})
-	err = cmdr.VolumesClient("example-go", "rm", "vol://myvolume/etc/hello.txt")
-	assert.NoError(t, err)
+
+	output := b.String()
+	assert.Contains(t, output, "WebDAV service for volume myvolume is running.")
+	assert.Contains(t, output, "Endpoint:")
+	assert.Contains(t, output, "Username:")
+	assert.Contains(t, output, "Password:")
 }
 
 func TestVolumesExpand(t *testing.T) {
diff --git a/internal/parser/volumes.go b/internal/parser/volumes.go
index 72e89e79..1818a815 100644
--- a/internal/parser/volumes.go
+++ b/internal/parser/volumes.go
@@ -30,7 +30,7 @@ func NewVolumesCommand(cmdr *commands.DryccCmd) *cobra.Command {
 	cmd.AddCommand(volumesExpandCommand(cmdr))
 	cmd.AddCommand(volumesInfoCommand(cmdr))
 	cmd.AddCommand(volumesRemoveCommand(cmdr))
-	cmd.AddCommand(volumesClientCommand(cmdr))
+	cmd.AddCommand(volumesServeCommand(cmdr))
 	cmd.AddCommand(volumesMountCommand(cmdr))
 	cmd.AddCommand(volumesUnmountCommand(cmdr))
 	return cmd
@@ -199,57 +199,27 @@ func volumesRemoveCommand(cmdr *commands.DryccCmd) *cobra.Command {
 	return cmd
 }
 
-func volumesClientCommand(cmdr *commands.DryccCmd) *cobra.Command {
-	cmd := &cobra.Command{
-		Use:   "client",
-		Short: i18n.T("The client used to manage volume files"),
+func volumesServeCommand(cmdr *commands.DryccCmd) *cobra.Command {
+	var flags struct {
+		name string
 	}
-	cmd.AddCommand(&cobra.Command{
-		Use: "ls <target>",
-		Example: template.CustomExample(
-			"drycc volumes client ls vol://myvolume/tmp",
-			map[string]string{
-				"<target>": i18n.T("The target path of volume"),
-			},
-		),
-		Short: i18n.T("List volume objects"),
-		Args:  cobra.ExactArgs(1),
-		RunE: func(_ *cobra.Command, args []string) error {
-			return cmdr.VolumesClient(app, "ls", args...)
-		},
-	})
-
-	cmd.AddCommand(&cobra.Command{
-		Use: "cp <source> <target>",
-		Example: template.CustomExample(
-			"drycc volumes client cp vol://myvolume/tmp /tmp",
-			map[string]string{
-				"<source>": i18n.T("The volume or local source path"),
-				"<target>": i18n.T("The volume or local target path"),
-			},
-		),
-		Short: i18n.T("Copy volume objects"),
-		Args:  cobra.ExactArgs(2),
-		RunE: func(_ *cobra.Command, args []string) error {
-			return cmdr.VolumesClient(app, "cp", args...)
-		},
-	})
-
-	cmd.AddCommand(&cobra.Command{
-		Use: "rm <target>",
+	volumeCompletion := completion.VolumeCompletion{AppID: &app, ArgsLen: 0, ConfigFile: &cmdr.ConfigFile}
+	cmd := &cobra.Command{
+		Use:   "serve <name>",
+		Short: i18n.T("Serve a WebDAV service for the volume"),
 		Example: template.CustomExample(
-			"drycc volumes client rm vol://myvolume/tmp",
+			"drycc volumes serve myvolume",
 			map[string]string{
-				"<target>": i18n.T("The target path of volume"),
+				"<name>": i18n.T("The volume name"),
 			},
 		),
-		Short: i18n.T("Remove volume objects"),
-		Args:  cobra.ExactArgs(1),
+		Args:              cobra.ExactArgs(1),
+		ValidArgsFunction: volumeCompletion.CompletionFunc,
 		RunE: func(_ *cobra.Command, args []string) error {
-			return cmdr.VolumesClient(app, "rm", args...)
+			flags.name = args[0]
+			return cmdr.VolumesServe(app, flags.name)
 		},
-	})
-
+	}
 	return cmd
 }
 
diff --git a/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po b/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
index 031f9338..d7014b6c 100644
--- a/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
+++ b/pkg/i18n/translations/drycc/en_US/LC_MESSAGES/cli.po
@@ -150,10 +150,6 @@ msgstr ""
 msgid "Configure automatic certificate management issuer details"
 msgstr ""
 
-#: internal/parser/volumes.go:227
-msgid "Copy volume objects"
-msgstr "Copy volume objects"
-
 #: internal/parser/apps.go:44
 msgid "Create a new application"
 msgstr ""
@@ -491,10 +487,6 @@ msgstr ""
 msgid "List tags for an application"
 msgstr ""
 
-#: internal/parser/volumes.go:211
-msgid "List volume objects"
-msgstr "List volume objects"
-
 #: internal/parser/volumes.go:134
 msgid "List volumes in the application"
 msgstr ""
@@ -884,10 +876,6 @@ msgstr ""
 msgid "Remove service from an application"
 msgstr ""
 
-#: internal/parser/volumes.go:242
-msgid "Remove volume objects"
-msgstr "Remove volume objects"
-
 #: internal/parser/keys.go:81
 msgid "Removes an SSH key for the logged in user"
 msgstr ""
@@ -1167,7 +1155,7 @@ msgid ""
 msgstr ""
 
 #: internal/parser/volumes.go:201
-msgid "The client used to manage volume files"
+msgid "Serve a WebDAV service for the volume"
 msgstr ""
 
 #: internal/parser/volumes.go:261 internal/parser/apps.go:158
@@ -1435,10 +1423,6 @@ msgstr ""
 msgid "The tag value"
 msgstr ""
 
-#: internal/parser/volumes.go:208 internal/parser/volumes.go:239
-msgid "The target path of volume"
-msgstr "The target path of volume"
-
 #: internal/parser/routes.go:187 internal/parser/routes.go:223
 msgid "The unique name of the gaetway"
 msgstr ""
@@ -1543,14 +1527,6 @@ msgstr ""
 msgid "The volume name to be removed"
 msgstr ""
 
-#: internal/parser/volumes.go:223
-msgid "The volume or local source path"
-msgstr "The volume or local source path"
-
-#: internal/parser/volumes.go:224
-msgid "The volume or local target path"
-msgstr "The volume or local target path"
-
 #: internal/parser/volumes.go:58 internal/parser/volumes.go:115
 msgid "The volume size, such as '500G'"
 msgstr ""
diff --git a/pkg/i18n/translations/drycc/template.pot b/pkg/i18n/translations/drycc/template.pot
index 58254628..0929c186 100644
--- a/pkg/i18n/translations/drycc/template.pot
+++ b/pkg/i18n/translations/drycc/template.pot
@@ -150,10 +150,6 @@ msgstr ""
 msgid "Configure automatic certificate management issuer details"
 msgstr ""
 
-#: internal/parser/volumes.go:227
-msgid "Copy volume objects"
-msgstr ""
-
 #: internal/parser/apps.go:44
 msgid "Create a new application"
 msgstr ""
@@ -484,10 +480,6 @@ msgstr ""
 msgid "List tags for an application"
 msgstr ""
 
-#: internal/parser/volumes.go:211
-msgid "List volume objects"
-msgstr ""
-
 #: internal/parser/volumes.go:134
 msgid "List volumes in the application"
 msgstr ""
@@ -877,10 +869,6 @@ msgstr ""
 msgid "Remove service from an application"
 msgstr ""
 
-#: internal/parser/volumes.go:242
-msgid "Remove volume objects"
-msgstr ""
-
 #: internal/parser/keys.go:81
 msgid "Removes an SSH key for the logged in user"
 msgstr ""
@@ -1155,7 +1143,7 @@ msgid ""
 msgstr ""
 
 #: internal/parser/volumes.go:201
-msgid "The client used to manage volume files"
+msgid "Serve a WebDAV service for the volume"
 msgstr ""
 
 #: internal/parser/volumes.go:261 internal/parser/apps.go:158
@@ -1428,10 +1416,6 @@ msgstr ""
 msgid "The tag value"
 msgstr ""
 
-#: internal/parser/volumes.go:208 internal/parser/volumes.go:239
-msgid "The target path of volume"
-msgstr ""
-
 #: internal/parser/routes.go:187 internal/parser/routes.go:223
 msgid "The unique name of the gaetway"
 msgstr ""
@@ -1536,14 +1520,6 @@ msgstr ""
 msgid "The volume name to be removed"
 msgstr ""
 
-#: internal/parser/volumes.go:223
-msgid "The volume or local source path"
-msgstr ""
-
-#: internal/parser/volumes.go:224
-msgid "The volume or local target path"
-msgstr ""
-
 #: internal/parser/volumes.go:58 internal/parser/volumes.go:115
 msgid "The volume size, such as '500G'"
 msgstr ""
diff --git a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
index 99338527..0ec35cd7 100644
--- a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
+++ b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
@@ -155,10 +155,6 @@ msgstr "要解绑的域名通用名称"
 msgid "Configure automatic certificate management issuer details"
 msgstr "配置自动证书管理颁发者详情"
 
-#: internal/parser/volumes.go:227
-msgid "Copy volume objects"
-msgstr "复制卷对象"
-
 #: internal/parser/apps.go:44
 msgid "Create a new application"
 msgstr "创建新应用"
@@ -501,10 +497,6 @@ msgstr "列出服务器规格信息"
 msgid "List tags for an application"
 msgstr "列出应用标签"
 
-#: internal/parser/volumes.go:211
-msgid "List volume objects"
-msgstr "列出卷对象"
-
 #: internal/parser/volumes.go:134
 msgid "List volumes in the application"
 msgstr "列出应用卷"
@@ -894,10 +886,6 @@ msgstr "从应用移除网关"
 msgid "Remove service from an application"
 msgstr "从应用移除服务"
 
-#: internal/parser/volumes.go:242
-msgid "Remove volume objects"
-msgstr "删除卷对象"
-
 #: internal/parser/keys.go:81
 msgid "Removes an SSH key for the logged in user"
 msgstr "移除登录用户的SSH密钥"
@@ -1244,8 +1232,8 @@ msgstr ""
 "                  exec探测接受参数列表，httpGet和tcpSocket探测需指定端口号"
 
 #: internal/parser/volumes.go:201
-msgid "The client used to manage volume files"
-msgstr "用于管理卷文件的客户端"
+msgid "Serve a WebDAV service for the volume"
+msgstr "为卷提供WebDAV服务"
 
 #: internal/parser/volumes.go:261 internal/parser/apps.go:158
 #: internal/parser/shortcuts.go:44
@@ -1512,10 +1500,6 @@ msgstr "要清除的标签键"
 msgid "The tag value"
 msgstr "标签值"
 
-#: internal/parser/volumes.go:208 internal/parser/volumes.go:239
-msgid "The target path of volume"
-msgstr "卷的目标路径"
-
 #: internal/parser/routes.go:187 internal/parser/routes.go:223
 msgid "The unique name of the gaetway"
 msgstr "网关唯一名称"
@@ -1620,14 +1604,6 @@ msgstr "要查询信息的卷名称"
 msgid "The volume name to be removed"
 msgstr "要移除的卷名称"
 
-#: internal/parser/volumes.go:223
-msgid "The volume or local source path"
-msgstr "The volume or local source path"
-
-#: internal/parser/volumes.go:224
-msgid "The volume or local target path"
-msgstr "卷或本地目标路径"
-
 #: internal/parser/volumes.go:58 internal/parser/volumes.go:115
 msgid "The volume size, such as '500G'"
 msgstr "卷容量（如 '500G'）"
