X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/441a934dd0e98d622cd1ea15054c4f5b4198f2be..42c20b25e1325124b88e3b9b285544dc41122b56:/lib/controller/localdb/collection.go diff --git a/lib/controller/localdb/collection.go b/lib/controller/localdb/collection.go index 2283b2fcb5..868e466e9e 100644 --- a/lib/controller/localdb/collection.go +++ b/lib/controller/localdb/collection.go @@ -68,7 +68,7 @@ func (conn *Conn) CollectionCreate(ctx context.Context, opts arvados.CreateOptio // 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) @@ -92,7 +92,7 @@ func (conn *Conn) CollectionUpdate(ctx context.Context, opts arvados.UpdateOptio // 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) @@ -122,64 +122,42 @@ func (conn *Conn) signCollection(ctx context.Context, coll *arvados.Collection) 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] @@ -190,7 +168,7 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s }) } - // 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 { @@ -201,7 +179,7 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s 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] != '/' { @@ -211,17 +189,17 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s 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 @@ -231,14 +209,14 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s } 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] @@ -247,20 +225,20 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s 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". @@ -268,20 +246,22 @@ func (conn *Conn) applySplices(ctx context.Context, fromUUID string, attrs map[s 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 }