// them.
opts.Select = append([]string{"is_trashed", "trash_at"}, opts.Select...)
}
- if err := conn.applySplices(ctx, "", opts.Attrs); err != nil {
+ if opts.Attrs, err = conn.applyReplaceFilesOption(ctx, "", opts.Attrs, opts.ReplaceFiles); err != nil {
return arvados.Collection{}, err
}
resp, err := conn.railsProxy.CollectionCreate(ctx, opts)
// them.
opts.Select = append([]string{"is_trashed", "trash_at"}, opts.Select...)
}
- if err := conn.applySplices(ctx, opts.UUID, opts.Attrs); err != nil {
+ if opts.Attrs, err = conn.applyReplaceFilesOption(ctx, opts.UUID, opts.Attrs, opts.ReplaceFiles); err != nil {
return arvados.Collection{}, err
}
resp, err := conn.railsProxy.CollectionUpdate(ctx, opts)
coll.ManifestText = arvados.SignManifest(coll.ManifestText, token, exp, ttl, []byte(conn.cluster.Collections.BlobSigningKey))
}
-// If attrs["splices"] is present, populate attrs["manifest_text"] by
+// If replaceFiles is non-empty, populate attrs["manifest_text"] by
// starting with the content of fromUUID (or an empty collection if
-// fromUUID is empty) and applying the specified splice operations.
-func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[string]interface{}) error {
- var splices map[string]string
-
- // Validate the incoming attrs, and return early if the
- // request doesn't ask for any splices.
- if sp, ok := attrs["splices"]; !ok {
- return nil
- } else {
- switch sp := sp.(type) {
- default:
- return httpserver.Errorf(http.StatusBadRequest, "invalid type %T for splices parameter", sp)
- case nil:
- return nil
- case map[string]string:
- splices = sp
- case map[string]interface{}:
- splices = make(map[string]string, len(sp))
- for dst, src := range sp {
- if src, ok := src.(string); ok {
- splices[dst] = src
- } else {
- return httpserver.Errorf(http.StatusBadRequest, "invalid source type for splice target %q: %v", dst, src)
- }
- }
- }
- if len(splices) == 0 {
- return nil
- } else if mtxt, ok := attrs["manifest_text"].(string); ok && len(mtxt) > 0 {
- return httpserver.Errorf(http.StatusBadRequest, "ambiguous request: both 'splices' and 'manifest_text' values provided")
- }
+// fromUUID is empty) and applying the specified file/directory
+// replacements.
+//
+// Return value is the (possibly modified) attrs map.
+func (conn *Conn) applyReplaceFilesOption(ctx context.Context, fromUUID string, attrs map[string]interface{}, replaceFiles map[string]string) (map[string]interface{}, error) {
+ if len(replaceFiles) == 0 {
+ return attrs, nil
+ } else if mtxt, ok := attrs["manifest_text"].(string); ok && len(mtxt) > 0 {
+ return nil, httpserver.Errorf(http.StatusBadRequest, "ambiguous request: both 'replace_files' and attrs['manifest_text'] values provided")
}
// Load the current collection (if any) and set up an
// in-memory filesystem.
var dst arvados.Collection
- if _, rootsplice := splices["/"]; !rootsplice && fromUUID != "" {
+ if _, replacingRoot := replaceFiles["/"]; !replacingRoot && fromUUID != "" {
src, err := conn.CollectionGet(ctx, arvados.GetOptions{UUID: fromUUID})
if err != nil {
- return err
+ return nil, err
}
dst = src
}
dstfs, err := dst.FileSystem(&arvados.StubClient{}, &arvados.StubClient{})
if err != nil {
- return err
+ return nil, err
}
- // Sort splices by source collection to avoid redundant
+ // Sort replacements by source collection to avoid redundant
// reloads when a source collection is used more than
// once. Note empty sources (which mean "delete target path")
// sort first.
- dstTodo := make([]string, 0, len(splices))
+ dstTodo := make([]string, 0, len(replaceFiles))
{
- srcid := make(map[string]string, len(splices))
- for dst, src := range splices {
+ srcid := make(map[string]string, len(replaceFiles))
+ for dst, src := range replaceFiles {
dstTodo = append(dstTodo, dst)
if i := strings.IndexRune(src, '/'); i > 0 {
srcid[dst] = src[:i]
})
}
- // Reject attempt to splice a node as well as its descendant
+ // Reject attempt to replace a node as well as its descendant
// (e.g., a/ and a/b/), which is unsupported, except where the
// source for a/ is empty (i.e., delete).
for _, dst := range dstTodo {
strings.Contains(dst, "/./") ||
strings.Contains(dst, "/../") ||
!strings.HasPrefix(dst, "/")) {
- return httpserver.Errorf(http.StatusBadRequest, "invalid splice target: %q", dst)
+ return nil, httpserver.Errorf(http.StatusBadRequest, "invalid replace_files target: %q", dst)
}
for i := 0; i < len(dst)-1; i++ {
if dst[i] != '/' {
if outerdst == "" {
outerdst = "/"
}
- if outersrc := splices[outerdst]; outersrc != "" {
- return httpserver.Errorf(http.StatusBadRequest, "cannot splice at target %q with non-empty splice at %q", dst, outerdst)
+ if outersrc := replaceFiles[outerdst]; outersrc != "" {
+ return nil, httpserver.Errorf(http.StatusBadRequest, "replace_files: cannot operate on target %q inside non-empty target %q", dst, outerdst)
}
}
}
var srcidloaded string
var srcfs arvados.FileSystem
- // Apply the requested splices.
+ // Apply the requested replacements.
for _, dst := range dstTodo {
- src := splices[dst]
+ src := replaceFiles[dst]
if src == "" {
if dst == "/" {
// In this case we started with a
}
err := dstfs.RemoveAll(dst)
if err != nil {
- return fmt.Errorf("RemoveAll(%s): %w", dst, err)
+ return nil, fmt.Errorf("RemoveAll(%s): %w", dst, err)
}
continue
}
srcspec := strings.SplitN(src, "/", 2)
srcid, srcpath := srcspec[0], "/"
if !arvadosclient.PDHMatch(srcid) {
- return httpserver.Errorf(http.StatusBadRequest, "invalid source %q for splices[%q]: must be \"\" or \"PDH\" or \"PDH/path\"", src, dst)
+ return nil, httpserver.Errorf(http.StatusBadRequest, "invalid source %q for replace_files[%q]: must be \"\" or \"PDH\" or \"PDH/path\"", src, dst)
}
if len(srcspec) == 2 && srcspec[1] != "" {
srcpath = srcspec[1]
srcfs = nil
srccoll, err := conn.CollectionGet(ctx, arvados.GetOptions{UUID: srcid})
if err != nil {
- return err
+ return nil, err
}
// We use StubClient here because we don't
// want srcfs to read/write any file data or
// sync collection state to/from the database.
srcfs, err = srccoll.FileSystem(&arvados.StubClient{}, &arvados.StubClient{})
if err != nil {
- return err
+ return nil, err
}
srcidloaded = srcid
}
snap, err := arvados.Snapshot(srcfs, srcpath)
if err != nil {
- return httpserver.Errorf(http.StatusBadRequest, "error getting snapshot of %q from %q: %w", srcpath, srcid, err)
+ return nil, httpserver.Errorf(http.StatusBadRequest, "error getting snapshot of %q from %q: %w", srcpath, srcid, err)
}
// Create intermediate dirs, in case dst is
// "newdir1/newdir2/dst".
if dst[i] == '/' {
err = dstfs.Mkdir(dst[:i], 0777)
if err != nil && !os.IsExist(err) {
- return httpserver.Errorf(http.StatusBadRequest, "error creating parent dirs for %q: %w", dst, err)
+ return nil, httpserver.Errorf(http.StatusBadRequest, "error creating parent dirs for %q: %w", dst, err)
}
}
}
err = arvados.Splice(dstfs, dst, snap)
if err != nil {
- return fmt.Errorf("error splicing snapshot onto path %q: %w", dst, err)
+ return nil, fmt.Errorf("error splicing snapshot onto path %q: %w", dst, err)
}
}
mtxt, err := dstfs.MarshalManifest(".")
if err != nil {
- return err
+ return nil, err
+ }
+ if attrs == nil {
+ attrs = make(map[string]interface{}, 1)
}
- delete(attrs, "splices")
attrs["manifest_text"] = mtxt
- return nil
+ return attrs, nil
}