|
1 | 1 | package commands |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
4 | 5 | "fmt" |
5 | | - "io" |
6 | | - "net/http" |
7 | 6 | "net/url" |
8 | 7 | "os" |
9 | | - "path" |
| 8 | + "os/signal" |
10 | 9 | "regexp" |
11 | 10 | "strings" |
| 11 | + "syscall" |
12 | 12 | "time" |
13 | 13 |
|
14 | | - drycc "github.com/drycc/controller-sdk-go" |
15 | 14 | "github.com/drycc/controller-sdk-go/api" |
16 | 15 | "github.com/drycc/controller-sdk-go/volumes" |
17 | 16 | "github.com/drycc/workflow-cli/internal/loader" |
@@ -161,17 +160,41 @@ func (d *DryccCmd) VolumesDelete(appID, name string) error { |
161 | 160 | return nil |
162 | 161 | } |
163 | 162 |
|
164 | | -// VolumesClient a client for manage volume file |
165 | | -func (d *DryccCmd) VolumesClient(appID, cmd string, args ...string) error { |
166 | | - switch cmd { |
167 | | - case "ls": |
168 | | - return d.volumesClientLs(appID, args[0]) |
169 | | - case "cp": |
170 | | - return d.volumesClientCp(appID, args[0], args[1]) |
171 | | - case "rm": |
172 | | - return d.volumesClientRm(appID, args[0]) |
173 | | - default: |
174 | | - return fmt.Errorf("unknown command %s", cmd) |
| 163 | +// VolumesServe Serve serves an app's volume. |
| 164 | +func (d *DryccCmd) VolumesServe(appID, name string) error { |
| 165 | + appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID) |
| 166 | + if err != nil { |
| 167 | + return err |
| 168 | + } |
| 169 | + parent, cancel := context.WithCancel(context.Background()) |
| 170 | + defer cancel() |
| 171 | + quit := progress(d.WOut) |
| 172 | + ctx, filer, err := volumes.Serve(parent, s.Client, appID, name) |
| 173 | + quit <- true |
| 174 | + <-quit |
| 175 | + if err != nil { |
| 176 | + return err |
| 177 | + } |
| 178 | + |
| 179 | + table := d.getDefaultFormatTable([]string{}) |
| 180 | + table.Append([]string{"Endpoint:", filer["endpoint"]}) |
| 181 | + table.Append([]string{"Username:", filer["username"]}) |
| 182 | + table.Append([]string{"Password:", filer["password"]}) |
| 183 | + table.Render() |
| 184 | + d.Print("\n") |
| 185 | + |
| 186 | + signalChan := make(chan os.Signal, 1) |
| 187 | + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2) |
| 188 | + d.Printf("WebDAV service for volume %s is running. Press Ctrl+C to stop.\n", name) |
| 189 | + for { |
| 190 | + select { |
| 191 | + case <-signalChan: |
| 192 | + return nil |
| 193 | + case <-ctx.Done(): |
| 194 | + return nil |
| 195 | + default: |
| 196 | + time.Sleep(2 * time.Second) |
| 197 | + } |
175 | 198 | } |
176 | 199 | } |
177 | 200 |
|
@@ -233,172 +256,6 @@ func (d *DryccCmd) VolumesUnmount(appID string, name string, volumeVars []string |
233 | 256 | return nil |
234 | 257 | } |
235 | 258 |
|
236 | | -// volumesClientLs get all directory entries sorted by filename. |
237 | | -func (d *DryccCmd) volumesClientLs(appID, vol string) error { |
238 | | - appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID) |
239 | | - if err != nil { |
240 | | - return err |
241 | | - } |
242 | | - |
243 | | - name, path, err := parseVol(vol) |
244 | | - if err != nil { |
245 | | - return err |
246 | | - } |
247 | | - dirs, _, err := volumes.ListDir(s.Client, appID, name, path, 3000) |
248 | | - if err != nil { |
249 | | - return err |
250 | | - } |
251 | | - |
252 | | - table := d.getDefaultFormatTable([]string{}) |
253 | | - for _, dir := range dirs { |
254 | | - if dir.Type == "dir" { |
255 | | - dir.Name = fmt.Sprintf("%s/", dir.Name) |
256 | | - } |
257 | | - table.Append([]string{fmt.Sprintf("[%s]", d.formatTime(dir.Timestamp)), dir.Size, dir.Name}) |
258 | | - } |
259 | | - table.Render() |
260 | | - return nil |
261 | | -} |
262 | | - |
263 | | -func (d *DryccCmd) volumesClientGetAll(client *drycc.Client, appID, volumeID, volumePath, localPath string) error { |
264 | | - if _, err := os.Stat(localPath); err != nil && os.IsNotExist(err) { |
265 | | - os.MkdirAll(localPath, os.ModePerm) |
266 | | - } |
267 | | - dirs, _, err := volumes.ListDir(client, appID, volumeID, volumePath, 3000) |
268 | | - if err != nil { |
269 | | - return err |
270 | | - } |
271 | | - for _, dir := range dirs { |
272 | | - _, subpath := path.Split(dir.Path) |
273 | | - filepath := path.Join(localPath, subpath) |
274 | | - if dir.Type == "file" { |
275 | | - res, err := volumes.GetFile(client, appID, volumeID, dir.Path) |
276 | | - if err != nil { |
277 | | - return err |
278 | | - } |
279 | | - w, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) |
280 | | - if err != nil { |
281 | | - return err |
282 | | - } |
283 | | - bar := d.newProgressbar(res.ContentLength, "↓", filepath) |
284 | | - defer w.Close() |
285 | | - if _, err = io.Copy(io.MultiWriter(w, bar), res.Body); err != nil { |
286 | | - return err |
287 | | - } |
288 | | - } else { |
289 | | - os.MkdirAll(filepath, os.ModePerm) |
290 | | - if err := d.volumesClientGetAll(client, appID, volumeID, dir.Path, filepath); err != nil { |
291 | | - return err |
292 | | - } |
293 | | - } |
294 | | - } |
295 | | - return nil |
296 | | -} |
297 | | - |
298 | | -func (d *DryccCmd) volumesClientPostAll(client *drycc.Client, appID, volumeID, volumePath, localPath string) error { |
299 | | - if file, err := os.Stat(localPath); err != nil { |
300 | | - return err |
301 | | - } else if !file.IsDir() { |
302 | | - file, err := os.Open(localPath) |
303 | | - if err != nil { |
304 | | - return err |
305 | | - } |
306 | | - defer file.Close() |
307 | | - |
308 | | - stat, err := file.Stat() |
309 | | - if err != nil { |
310 | | - return err |
311 | | - } |
312 | | - if stat.Size() > 0 { // ignore empty file |
313 | | - reader := progressbar.NewReader(file, d.newProgressbar(stat.Size(), "↑", localPath)) |
314 | | - if _, err := volumes.PostFile(client, appID, volumeID, volumePath, file.Name(), stat.Size(), &reader); err != nil { |
315 | | - return err |
316 | | - } |
317 | | - } else { |
318 | | - d.newProgressbar(1, "?", localPath).Finish() |
319 | | - } |
320 | | - return nil |
321 | | - } |
322 | | - if entries, err := os.ReadDir(localPath); err == nil { |
323 | | - for _, entry := range entries { |
324 | | - var dstFilepath string |
325 | | - if entry.IsDir() { |
326 | | - dstFilepath = path.Join(volumePath, entry.Name()) |
327 | | - } else { |
328 | | - dstFilepath = volumePath |
329 | | - } |
330 | | - if err := d.volumesClientPostAll(client, appID, volumeID, dstFilepath, path.Join(localPath, entry.Name())); err != nil { |
331 | | - return err |
332 | | - } |
333 | | - } |
334 | | - } else { |
335 | | - return err |
336 | | - } |
337 | | - return nil |
338 | | -} |
339 | | - |
340 | | -// volumesClientCp copy files between volume and local file |
341 | | -func (d *DryccCmd) volumesClientCp(appID, src, dst string) error { |
342 | | - appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID) |
343 | | - if err != nil { |
344 | | - return err |
345 | | - } |
346 | | - if strings.HasPrefix(src, "vol://") { |
347 | | - f, err := os.Stat(dst) |
348 | | - if err != nil { |
349 | | - return err |
350 | | - } |
351 | | - if !f.IsDir() { |
352 | | - return fmt.Errorf("the local path must be an existing dir") |
353 | | - } |
354 | | - volumeID, volumePath, err := parseVol(src) |
355 | | - if err != nil { |
356 | | - return err |
357 | | - } |
358 | | - if dirs, _, err := volumes.ListDir(s.Client, appID, volumeID, volumePath, 3000); err == nil && (len(dirs) != 1 || dirs[0].Type != "file") { |
359 | | - dst = mergeDestDir(dst, volumePath) |
360 | | - } |
361 | | - return d.volumesClientGetAll(s.Client, appID, volumeID, volumePath, dst) |
362 | | - } else if strings.HasPrefix(dst, "vol://") { |
363 | | - volumeID, volumePath, err := parseVol(dst) |
364 | | - if err != nil { |
365 | | - return err |
366 | | - } |
367 | | - if dirs, _, err := volumes.ListDir(s.Client, appID, volumeID, volumePath, 3000); err == nil { |
368 | | - names := strings.Split(strings.Trim(src, "/"), "/") |
369 | | - if len(dirs) == 1 && dirs[0].Type == "file" && strings.HasSuffix(strings.Trim(volumePath, "/"), names[len(names)-1]) { |
370 | | - return fmt.Errorf("the volume path cannot be an existing file") |
371 | | - } |
372 | | - } |
373 | | - if file, err := os.Stat(src); err == nil && file.IsDir() { |
374 | | - volumePath = mergeDestDir(volumePath, src) |
375 | | - } |
376 | | - return d.volumesClientPostAll(s.Client, appID, volumeID, volumePath, src) |
377 | | - } |
378 | | - return nil |
379 | | -} |
380 | | - |
381 | | -// volumesClientRm delete a file from volume |
382 | | -func (d *DryccCmd) volumesClientRm(appID, vol string) error { |
383 | | - appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID) |
384 | | - if err != nil { |
385 | | - return err |
386 | | - } |
387 | | - host, path, err := parseVol(vol) |
388 | | - if err != nil { |
389 | | - return err |
390 | | - } |
391 | | - res, err := volumes.DeleteFile(s.Client, appID, host, path) |
392 | | - if err != nil { |
393 | | - return err |
394 | | - } |
395 | | - if res.StatusCode != http.StatusOK { |
396 | | - return fmt.Errorf("incorrect http status code %d", res.StatusCode) |
397 | | - } |
398 | | - |
399 | | - return nil |
400 | | -} |
401 | | - |
402 | 259 | func parseVolume(volumeVars []string) (map[string]any, error) { |
403 | 260 | volumeMap := make(map[string]any) |
404 | 261 | regex := regexp.MustCompile(`^([a-z0-9]+(?:-[a-z0-9]+)*)=(\/([\w]+[\w-]*\/?)+)$`) |
|
0 commit comments