cmd/uplinkng: rm
this implements the rm command which has to add a Remove method to the fileystem interface and implement it for local, remote and test filesystems. Change-Id: Id41add28f01938893530aae0b4b73c8954e9b715
This commit is contained in:
parent
173d1e638c
commit
ce87652a8c
@ -63,13 +63,12 @@ func (c *cmdCp) copyRecursive(ctx clingy.Context, fs ulfs.Filesystem) error {
|
||||
return errs.New("cannot recursively copy to stdin/stdout")
|
||||
}
|
||||
|
||||
var anyFailed bool
|
||||
|
||||
iter, err := fs.ListObjects(ctx, c.source, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
anyFailed := false
|
||||
for iter.Next() {
|
||||
rel, err := c.source.RelativeTo(iter.Item().Loc)
|
||||
if err != nil {
|
||||
|
@ -113,5 +113,6 @@ func TestCpRecursiveDifficult(t *testing.T) {
|
||||
ultest.WithFile("sj://user/mid-slash/1"),
|
||||
)
|
||||
|
||||
// TODO(jeff): these tests. oops.
|
||||
_ = state
|
||||
}
|
||||
|
@ -4,9 +4,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/zeebo/clingy"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/cmd/uplinkng/ulloc"
|
||||
)
|
||||
@ -43,9 +45,36 @@ func (c *cmdRm) Execute(ctx clingy.Context) error {
|
||||
}
|
||||
defer func() { _ = fs.Close() }()
|
||||
|
||||
// TODO: use the filesystem interface
|
||||
// TODO: recursive remove
|
||||
if !c.recursive {
|
||||
if err := fs.Remove(ctx, c.location); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// return fs.Delete(ctx, c.location)
|
||||
fmt.Fprintln(ctx.Stdout(), "removed", c.location)
|
||||
return nil
|
||||
}
|
||||
|
||||
iter, err := fs.ListObjects(ctx, c.location, c.recursive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
anyFailed := false
|
||||
for iter.Next() {
|
||||
loc := iter.Item().Loc
|
||||
|
||||
if err := fs.Remove(ctx, loc); err != nil {
|
||||
fmt.Fprintln(ctx.Stderr(), "remove", loc, "failed:", err.Error())
|
||||
anyFailed = true
|
||||
} else {
|
||||
fmt.Fprintln(ctx.Stdout(), "removed", loc)
|
||||
}
|
||||
}
|
||||
|
||||
if err := iter.Err(); err != nil {
|
||||
return errs.Wrap(err)
|
||||
} else if anyFailed {
|
||||
return errs.New("some removals failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
72
cmd/uplinkng/cmd_rm_test.go
Normal file
72
cmd/uplinkng/cmd_rm_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"storj.io/storj/cmd/uplinkng/ultest"
|
||||
)
|
||||
|
||||
func TestRmRemote(t *testing.T) {
|
||||
state := ultest.Setup(commands,
|
||||
ultest.WithFile("sj://user/file1.txt"),
|
||||
ultest.WithFile("sj://user/file2.txt"),
|
||||
ultest.WithFile("/home/user/file1.txt"),
|
||||
ultest.WithFile("/home/user/file2.txt"),
|
||||
)
|
||||
|
||||
state.Succeed(t, "rm", "sj://user/file1.txt").RequireFiles(t,
|
||||
ultest.File{Loc: "sj://user/file2.txt"},
|
||||
ultest.File{Loc: "/home/user/file1.txt"},
|
||||
ultest.File{Loc: "/home/user/file2.txt"},
|
||||
)
|
||||
}
|
||||
|
||||
func TestRmRemoteRecursive(t *testing.T) {
|
||||
state := ultest.Setup(commands,
|
||||
ultest.WithFile("sj://user/file1.txt"),
|
||||
ultest.WithFile("sj://user/file2.txt"),
|
||||
ultest.WithFile("sj://user/other_file1.txt"),
|
||||
ultest.WithFile("/home/user/file1.txt"),
|
||||
ultest.WithFile("/home/user/file2.txt"),
|
||||
)
|
||||
|
||||
state.Succeed(t, "rm", "sj://user/file", "-r").RequireFiles(t,
|
||||
ultest.File{Loc: "sj://user/other_file1.txt"},
|
||||
ultest.File{Loc: "/home/user/file1.txt"},
|
||||
ultest.File{Loc: "/home/user/file2.txt"},
|
||||
)
|
||||
}
|
||||
|
||||
func TestRmLocal(t *testing.T) {
|
||||
state := ultest.Setup(commands,
|
||||
ultest.WithFile("sj://user/file1.txt"),
|
||||
ultest.WithFile("sj://user/file2.txt"),
|
||||
ultest.WithFile("/home/user/file1.txt"),
|
||||
ultest.WithFile("/home/user/file2.txt"),
|
||||
)
|
||||
|
||||
state.Succeed(t, "rm", "/home/user/file1.txt").RequireFiles(t,
|
||||
ultest.File{Loc: "sj://user/file1.txt"},
|
||||
ultest.File{Loc: "sj://user/file2.txt"},
|
||||
ultest.File{Loc: "/home/user/file2.txt"},
|
||||
)
|
||||
}
|
||||
|
||||
func TestRmLocalRecursive(t *testing.T) {
|
||||
state := ultest.Setup(commands,
|
||||
ultest.WithFile("sj://user/file1.txt"),
|
||||
ultest.WithFile("sj://user/file2.txt"),
|
||||
ultest.WithFile("/home/user/file1.txt"),
|
||||
ultest.WithFile("/home/user/file2.txt"),
|
||||
ultest.WithFile("/home/user/other_file1.txt"),
|
||||
)
|
||||
|
||||
state.Succeed(t, "rm", "/home/user/file", "-r").RequireFiles(t,
|
||||
ultest.File{Loc: "sj://user/file1.txt"},
|
||||
ultest.File{Loc: "sj://user/file2.txt"},
|
||||
ultest.File{Loc: "/home/user/other_file1.txt"},
|
||||
)
|
||||
}
|
@ -22,6 +22,7 @@ type Filesystem interface {
|
||||
Close() error
|
||||
Open(ctx clingy.Context, loc ulloc.Location) (ReadHandle, error)
|
||||
Create(ctx clingy.Context, loc ulloc.Location) (WriteHandle, error)
|
||||
Remove(ctx context.Context, loc ulloc.Location) error
|
||||
ListObjects(ctx context.Context, prefix ulloc.Location, recursive bool) (ObjectIterator, error)
|
||||
ListUploads(ctx context.Context, prefix ulloc.Location, recursive bool) (ObjectIterator, error)
|
||||
IsLocalDir(ctx context.Context, loc ulloc.Location) bool
|
||||
|
@ -75,6 +75,22 @@ func (l *Local) Create(ctx context.Context, path string) (WriteHandle, error) {
|
||||
return newOSWriteHandle(fh), nil
|
||||
}
|
||||
|
||||
// Remove unlinks the file at the path. It is not an error if the file does not exist.
|
||||
func (l *Local) Remove(ctx context.Context, path string) error {
|
||||
path, err := l.abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(path); os.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects returns an ObjectIterator listing files and directories that have string prefix
|
||||
// with the provided path.
|
||||
func (l *Local) ListObjects(ctx context.Context, path string, recursive bool) (ObjectIterator, error) {
|
||||
|
@ -51,6 +51,16 @@ func (m *Mixed) Create(ctx clingy.Context, loc ulloc.Location) (WriteHandle, err
|
||||
return newGenericWriteHandle(ctx.Stdout()), nil
|
||||
}
|
||||
|
||||
// Remove deletes either a local file or remote object.
|
||||
func (m *Mixed) Remove(ctx context.Context, loc ulloc.Location) error {
|
||||
if bucket, key, ok := loc.RemoteParts(); ok {
|
||||
return m.remote.Remove(ctx, bucket, key)
|
||||
} else if path, ok := loc.LocalParts(); ok {
|
||||
return m.local.Remove(ctx, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects lists either files and directories with some local path prefix or remote objects
|
||||
// with a given bucket and key.
|
||||
func (m *Mixed) ListObjects(ctx context.Context, prefix ulloc.Location, recursive bool) (ObjectIterator, error) {
|
||||
|
@ -47,6 +47,15 @@ func (r *Remote) Create(ctx context.Context, bucket, key string) (WriteHandle, e
|
||||
return newUplinkWriteHandle(fh), nil
|
||||
}
|
||||
|
||||
// Remove deletes the object at the provided key and bucket.
|
||||
func (r *Remote) Remove(ctx context.Context, bucket, key string) error {
|
||||
_, err := r.project.DeleteObject(ctx, bucket, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListObjects lists all of the objects in some bucket that begin with the given prefix.
|
||||
func (r *Remote) ListObjects(ctx context.Context, bucket, prefix string, recursive bool) ObjectIterator {
|
||||
parentPrefix := ""
|
||||
|
@ -88,6 +88,11 @@ func (tfs *testFilesystem) Create(ctx clingy.Context, loc ulloc.Location) (_ ulf
|
||||
return wh, nil
|
||||
}
|
||||
|
||||
func (tfs *testFilesystem) Remove(ctx context.Context, loc ulloc.Location) error {
|
||||
delete(tfs.files, loc)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tfs *testFilesystem) ListObjects(ctx context.Context, prefix ulloc.Location, recursive bool) (ulfs.ObjectIterator, error) {
|
||||
var infos []ulfs.ObjectInfo
|
||||
for loc, mf := range tfs.files {
|
||||
|
@ -53,10 +53,18 @@ func (r Result) RequireStderr(t *testing.T, stderr string) Result {
|
||||
}
|
||||
|
||||
// RequireFiles requires that the set of files provided are all of the files that
|
||||
// existed at the end of the execution.
|
||||
// existed at the end of the execution. It assumes any passed in files with no
|
||||
// contents contain the filename as the contents instead.
|
||||
func (r Result) RequireFiles(t *testing.T, files ...File) Result {
|
||||
files = append([]File(nil), files...)
|
||||
sort.Slice(files, func(i, j int) bool { return files[i].less(files[j]) })
|
||||
|
||||
for i := range files {
|
||||
if files[i].Contents == "" {
|
||||
files[i].Contents = files[i].Loc
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, files, r.Files)
|
||||
return r
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user