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:
Jeff Wendling 2021-05-14 15:20:21 -04:00
parent 173d1e638c
commit ce87652a8c
10 changed files with 156 additions and 6 deletions

View File

@ -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 {

View File

@ -113,5 +113,6 @@ func TestCpRecursiveDifficult(t *testing.T) {
ultest.WithFile("sj://user/mid-slash/1"),
)
// TODO(jeff): these tests. oops.
_ = state
}

View File

@ -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
}

View 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"},
)
}

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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 := ""

View File

@ -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 {

View File

@ -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
}