From b5bf23979b6e865c2d4d9eee3f01aa355e3355c8 Mon Sep 17 00:00:00 2001 From: Alex Aizman Date: Fri, 29 Nov 2024 10:48:33 -0500 Subject: [PATCH] CLI w/ embedded prefix: disambiguating * add `dop`, `oltp` structures; refactor * compensate for s3 delete deficiency * consolidate all errors in cli/err.go * prefetch to warn "does not exist"; auto-confirm * simplify and refactor * part two, prev. commit: 1bf78c86a48 Signed-off-by: Alex Aizman --- ais/proxy.go | 5 +- ais/test/scripts/remais-prefetch-latest.sh | 10 +- ais/test/scripts/s3-prefetch-latest-prefix.sh | 10 +- cmd/cli/cli/bucket_hdlr.go | 4 +- cmd/cli/cli/err.go | 46 ++++++ cmd/cli/cli/get.go | 5 - cmd/cli/cli/job_hdlr.go | 3 +- cmd/cli/cli/multiobj.go | 59 ++++--- cmd/cli/cli/object_hdlr.go | 2 +- cmd/cli/cli/parse_uri.go | 144 +++++++++++------- cmd/cli/cli/tcbtco.go | 26 ++-- cmd/cli/cli/utils.go | 51 ------- cmd/cli/cli/yap.go | 13 +- 13 files changed, 216 insertions(+), 162 deletions(-) diff --git a/ais/proxy.go b/ais/proxy.go index 0e9d5b4a2c..90ef249345 100644 --- a/ais/proxy.go +++ b/ais/proxy.go @@ -2238,8 +2238,9 @@ func (p *proxy) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.Qu hdr := w.Header() hdr.Set(cos.HdrContentType, res.header.Get(cos.HdrContentType)) hdr.Set(cos.HdrContentLength, strconv.Itoa(len(res.bytes))) - _, err = w.Write(res.bytes) - debug.AssertNoErr(err) + if _, err := w.Write(res.bytes); err != nil { + nlog.Warningln(err) // (broken pipe; benign) + } } func (p *proxy) redirectURL(r *http.Request, si *meta.Snode, ts time.Time, netIntra string, netPubs ...string) (redirect string) { diff --git a/ais/test/scripts/remais-prefetch-latest.sh b/ais/test/scripts/remais-prefetch-latest.sh index 22289d2675..7453dbf53d 100755 --- a/ais/test/scripts/remais-prefetch-latest.sh +++ b/ais/test/scripts/remais-prefetch-latest.sh @@ -123,8 +123,9 @@ cnt3=$(ais show performance counters --regex $cold_counter -H | awk '{sum+=$2;}E echo "9. out-of-band DELETE" AIS_ENDPOINT=$rendpoint ais object rm "$rbucket/lorem-duis" 1>/dev/null || exit $? -echo "10. prefetch without '--latest': expecting no changes" -ais prefetch "$bucket/lorem-duis" --wait +## '--yes' to auto-confirm non-existence +echo "10. prefetch without '--latesti --yes': expecting no changes" +ais prefetch "$bucket/lorem-duis" --wait --yes checksum=$(ais ls "$bucket/lorem-duis" --cached -H -props checksum | awk '{print $2}') [[ "$checksum" == "$sum2" ]] || { echo "FAIL: $checksum != $sum2"; exit 1; } @@ -132,8 +133,9 @@ echo "11. remember 'remote-deleted' counter, enable version synchronization on t cnt4=$(ais show performance counters --regex REMOTE-DEL -H | awk '{sum+=$2;}END{print sum;}') ais bucket props set $bucket versioning.synchronize=true -echo "12. run 'prefetch --latest' one last time, and make sure the object \"disappears\"" -ais prefetch "$bucket/lorem-duis" --latest --wait 2>/dev/null +## '--yes' ditto +echo "12. run 'prefetch --latest --yes' one last time, and make sure the object \"disappears\"" +ais prefetch "$bucket/lorem-duis" --latest --wait --yes 2>/dev/null [[ $? == 0 ]] || { echo "FAIL: expecting 'prefetch --wait' to return Ok, got $?"; exit 1; } echo "13. 'remote-deleted' counter must increment" diff --git a/ais/test/scripts/s3-prefetch-latest-prefix.sh b/ais/test/scripts/s3-prefetch-latest-prefix.sh index 0b6f2c2dfb..173df11e4b 100755 --- a/ais/test/scripts/s3-prefetch-latest-prefix.sh +++ b/ais/test/scripts/s3-prefetch-latest-prefix.sh @@ -105,8 +105,9 @@ cnt3=$(ais show performance counters --regex ${cold_counter} -H | awk '{sum+=$2; echo "9. out-of-band DELETE" s3cmd del "$bucket/lorem-duis" $host 1>/dev/null || exit $? -echo "10. prefetch without '--latest': expecting no changes" -ais prefetch "$bucket/lorem-duis" --wait +## '--yes' to auto-confirm non-existence +echo "10. prefetch without '--latest --yes': expecting no changes" +ais prefetch "$bucket/lorem-duis" --wait --yes checksum=$(ais ls "$bucket/lorem-duis" --cached -H -props checksum | awk '{print $2}') [[ "$checksum" == "$sum2" ]] || { echo "FAIL: $checksum != $sum2"; exit 1; } @@ -114,8 +115,9 @@ echo "11. remember 'remote-deleted' counter _and_ enable version synchronization cnt4=$(ais show performance counters --regex REMOTE-DEL -H | awk '{sum+=$2;}END{print sum;}') ais bucket props set $bucket versioning.synchronize=true -echo "12. run 'prefetch --latest' one last time, and make sure the object \"disappears\"" -ais prefetch "$bucket/lorem-duis" --latest --wait 2>/dev/null +## '--yes' ditto +echo "12. run 'prefetch --latest --yes' one last time, and make sure the object \"disappears\"" +ais prefetch "$bucket/lorem-duis" --latest --wait --yes 2>/dev/null [[ $? == 0 ]] || { echo "FAIL: expecting 'prefetch --wait' to return Ok, got $?"; exit 1; } echo "13. 'remote-deleted' counter must increment (because 'versioning.synchronize=true')" diff --git a/cmd/cli/cli/bucket_hdlr.go b/cmd/cli/cli/bucket_hdlr.go index 3c18e1736d..de02f4b8cb 100644 --- a/cmd/cli/cli/bucket_hdlr.go +++ b/cmd/cli/cli/bucket_hdlr.go @@ -130,7 +130,7 @@ var ( waitJobXactFinishedFlag, latestVerFlag, syncFlag, - noRecursFlag, // (embedded prefix parseObjListTemplate dop) + noRecursFlag, // (embedded prefix dopOLTP) nonverboseFlag, }, commandRename: { @@ -143,7 +143,7 @@ var ( keepMDFlag, verbObjPrefixFlag, // to disambiguate bucket/prefix vs bucket/objName dryRunFlag, - noRecursFlag, // (embedded prefix parseObjListTemplate dop) + noRecursFlag, // (embedded prefix dopOLTP) verboseFlag, // not yet used nonverboseFlag, ), diff --git a/cmd/cli/cli/err.go b/cmd/cli/cli/err.go index 03b5343c7d..2eab63b80a 100644 --- a/cmd/cli/cli/err.go +++ b/cmd/cli/cli/err.go @@ -426,3 +426,49 @@ func isStartingUp(err error) bool { } return false } + +// +// misplaced or mistyped flag(s) +// + +func errArgIsFlag(c *cli.Context, arg string) (err error) { + if len(arg) > 1 && arg[0] == '-' { + err = incorrectUsageMsg(c, "missing command line argument (hint: flag '%s' misplaced?)", arg) + } + return err +} + +func errTailArgsContainFlag(tail []string) error { + for _, arg := range tail { + if len(arg) > 1 && arg[0] == '-' { + return fmt.Errorf("unrecognized or misplaced option %q", arg) + } + } + return nil +} + +// +// range read +// + +func errRangeReadArch(what string) error { + return fmt.Errorf("cannot range-read (%s, %s) archived content (%s) - "+NIY, qflprn(lengthFlag), qflprn(offsetFlag), what) +} + +// +// parse uri +// + +func errBucketNameInvalid(c *cli.Context, arg string, err error) error { + if errV := errArgIsFlag(c, arg); errV != nil { + return errV + } + if strings.Contains(err.Error(), cos.OnlyPlus) && strings.Contains(err.Error(), "bucket name") { + if strings.Contains(arg, ":/") && !strings.Contains(arg, apc.BckProviderSeparator) { + a := strings.Replace(arg, ":/", apc.BckProviderSeparator, 1) + return fmt.Errorf("bucket name in %q is invalid: (did you mean %q?)", arg, a) + } + return fmt.Errorf("bucket name in %q is invalid: "+cos.OnlyPlus, arg) + } + return nil +} diff --git a/cmd/cli/cli/get.go b/cmd/cli/cli/get.go index 2a238d992e..c95f9913ae 100644 --- a/cmd/cli/cli/get.go +++ b/cmd/cli/cli/get.go @@ -730,8 +730,3 @@ func (ex *extractor) _write(filename string, size int64, wfh *os.File, reader io func discardOutput(outf string) bool { return outf == "/dev/null" || outf == "dev/null" || outf == "dev/nil" } - -// rr -func errRangeReadArch(what string) error { - return fmt.Errorf("cannot range-read (%s, %s) archived content (%s) - "+NIY, qflprn(lengthFlag), qflprn(offsetFlag), what) -} diff --git a/cmd/cli/cli/job_hdlr.go b/cmd/cli/cli/job_hdlr.go index a4c99ab3bf..608d959a6f 100644 --- a/cmd/cli/cli/job_hdlr.go +++ b/cmd/cli/cli/job_hdlr.go @@ -101,8 +101,9 @@ var ( dryRunFlag, verbObjPrefixFlag, latestVerFlag, - noRecursFlag, // (embedded prefix parseObjListTemplate dop) + noRecursFlag, // (embedded prefix dopOLTP) blobThresholdFlag, + yesFlag, numListRangeWorkersFlag, ), cmdBlobDownload: { diff --git a/cmd/cli/cli/multiobj.go b/cmd/cli/cli/multiobj.go index be382c2618..e3f283f3d4 100644 --- a/cmd/cli/cli/multiobj.go +++ b/cmd/cli/cli/multiobj.go @@ -173,22 +173,22 @@ func _evictOne(c *cli.Context, shift int) error { if _, err := headBucket(bck, false /* don't add */); err != nil { return err } - objName, listObjs, tmplObjs, err := parseObjListTemplate(c, bck, objNameOrTmpl) + oltp, err := dopOLTP(c, bck, objNameOrTmpl) if err != nil { return err } switch { - case listObjs != "" || tmplObjs != "": // 1. multi-obj - lrCtx := &lrCtx{listObjs, tmplObjs, bck} + case oltp.list != "" || oltp.tmpl != "": // 1. multi-obj + lrCtx := &lrCtx{oltp.list, oltp.tmpl, bck} return lrCtx.do(c) - case objName == "": // 2. entire bucket + case oltp.objName == "": // 2. entire bucket return evictBucket(c, bck) default: // 3. one(?) obj to evict - err := api.EvictObject(apiBP, bck, objName) + err := api.EvictObject(apiBP, bck, oltp.objName) if err == nil { if !flagIsSet(c, nonverboseFlag) { - fmt.Fprintf(c.App.Writer, "evicted %q from %s\n", objName, bck.Cname("")) + fmt.Fprintf(c.App.Writer, "evicted %q from %s\n", oltp.objName, bck.Cname("")) } return nil } @@ -201,7 +201,7 @@ func _evictOne(c *cli.Context, shift int) error { if !argIsFlag(c, 1) { suffix = " (hint: missing double or single quotes?)" } - return &errDoesNotExist{name: bck.Cname(objName), suffix: suffix} + return &errDoesNotExist{name: bck.Cname(oltp.objName), suffix: suffix} } } @@ -230,18 +230,19 @@ func _rmOne(c *cli.Context, shift int) error { if _, err := headBucket(bck, false /* don't add */); err != nil { return err } - // NOTE: passing empty bck _not_ to interpret embedded objName as prefix - // TODO: instead of HEAD(obj) do list-objects(prefix=objNameOrTmpl) - here and everywhere - objName, listObjs, tmplObjs, err := parseObjListTemplate(c, bck, objNameOrTmpl) + // [NOTE] + // - passing empty bck _not_ to interpret embedded objName as prefix + // - instead of HEAD(obj) do list-objects(prefix=objNameOrTmpl) - here and everywhere + oltp, err := dopOLTP(c, bck, objNameOrTmpl) if err != nil { return err } switch { - case listObjs != "" || tmplObjs != "": // 1. multi-obj - lrCtx := &lrCtx{listObjs, tmplObjs, bck} + case oltp.list != "" || oltp.tmpl != "": // 1. multi-obj + lrCtx := &lrCtx{oltp.list, oltp.tmpl, bck} return lrCtx.do(c) - case objName == "": // 2. all objects + case oltp.objName == "": // 2. all objects if flagIsSet(c, rmrfFlag) { if !flagIsSet(c, yesFlag) { warn := fmt.Sprintf("will remove all objects from %s. The operation cannot be undone!", bck) @@ -251,13 +252,19 @@ func _rmOne(c *cli.Context, shift int) error { } return rmRfAllObjects(c, bck) } - return incorrectUsageMsg(c, "use one of: (%s or %s or %s) to indicate _which_ objects to remove", + return incorrectUsageMsg(c, "to select objects to be removed use one of: (%s or %s or %s)", qflprn(listFlag), qflprn(templateFlag), qflprn(rmrfFlag)) default: // 3. one obj - err := api.DeleteObject(apiBP, bck, objName) + err := api.DeleteObject(apiBP, bck, oltp.objName) + if err == nil && oltp.notFound { + // [NOTE] + // - certain backends return OK when specified object does not exist (see aws.go) + // - compensate here + return cos.NewErrNotFound(nil, bck.Cname(oltp.objName)) + } if err == nil { if !flagIsSet(c, nonverboseFlag) { - fmt.Fprintf(c.App.Writer, "deleted %q from %s\n", objName, bck.Cname("")) + fmt.Fprintf(c.App.Writer, "deleted %q from %s\n", oltp.objName, bck.Cname("")) } return nil } @@ -270,7 +277,7 @@ func _rmOne(c *cli.Context, shift int) error { if c.NArg() > 1 { suffix = " (hint: missing double or single quotes?)" } - return &errDoesNotExist{name: bck.Cname(objName), suffix: suffix} + return &errDoesNotExist{name: bck.Cname(oltp.objName), suffix: suffix} } } @@ -303,15 +310,25 @@ func _prefetchOne(c *cli.Context, shift int) error { if !bck.IsRemote() { return fmt.Errorf("expecting remote bucket (have %s)", bck.Cname("")) } - objName, listObjs, tmplObjs, err := parseObjListTemplate(c, bck, objNameOrTmpl) + oltp, err := dopOLTP(c, bck, objNameOrTmpl) if err != nil { return err } + if oltp.notFound { // (true only when list-objects says so) + err := cos.NewErrNotFound(nil, "\""+uri+"\"") + if !flagIsSet(c, yesFlag) { + if ok := confirm(c, err.Error()+" - proceed anyway?"); !ok { + return err + } + } else { + actionWarn(c, err.Error()+" - proceeding anyway") + } + } - if listObjs == "" && tmplObjs == "" { - listObjs = objName + if oltp.list == "" && oltp.tmpl == "" { + oltp.list = oltp.objName // ("prefetch" is not one of those primitive verbs) } - lrCtx := &lrCtx{listObjs, tmplObjs, bck} + lrCtx := &lrCtx{oltp.list, oltp.tmpl, bck} return lrCtx.do(c) } diff --git a/cmd/cli/cli/object_hdlr.go b/cmd/cli/cli/object_hdlr.go index 1a1c06ed3d..705bca1f56 100644 --- a/cmd/cli/cli/object_hdlr.go +++ b/cmd/cli/cli/object_hdlr.go @@ -81,7 +81,7 @@ var ( rmrfFlag, verboseFlag, // rm -rf nonverboseFlag, - noRecursFlag, // (embedded prefix parseObjListTemplate dop) + noRecursFlag, // (embedded prefix dopOLTP dop) yesFlag, ), commandRename: {}, diff --git a/cmd/cli/cli/parse_uri.go b/cmd/cli/cli/parse_uri.go index 54b8ed716f..884c6f10a8 100644 --- a/cmd/cli/cli/parse_uri.go +++ b/cmd/cli/cli/parse_uri.go @@ -9,26 +9,12 @@ import ( "fmt" "strings" + "github.com/NVIDIA/aistore/api" "github.com/NVIDIA/aistore/api/apc" "github.com/NVIDIA/aistore/cmn" - "github.com/NVIDIA/aistore/cmn/cos" "github.com/urfave/cli" ) -func errBucketNameInvalid(c *cli.Context, arg string, err error) error { - if errV := errArgIsFlag(c, arg); errV != nil { - return errV - } - if strings.Contains(err.Error(), cos.OnlyPlus) && strings.Contains(err.Error(), "bucket name") { - if strings.Contains(arg, ":/") && !strings.Contains(arg, apc.BckProviderSeparator) { - a := strings.Replace(arg, ":/", apc.BckProviderSeparator, 1) - return fmt.Errorf("bucket name in %q is invalid: (did you mean %q?)", arg, a) - } - return fmt.Errorf("bucket name in %q is invalid: "+cos.OnlyPlus, arg) - } - return nil -} - // Return `bckFrom` and `bckTo` - the [shift] and the [shift+1] arguments, respectively func parseBcks(c *cli.Context, bckFromArg, bckToArg string, shift int, optionalSrcObjname bool) (bckFrom, bckTo cmn.Bck, objFrom string, err error) { @@ -193,62 +179,112 @@ func parseBckObjURI(c *cli.Context, uri string, emptyObjnameOK bool) (bck cmn.Bc return bck, objName, err } -func parseObjListTemplate(c *cli.Context, bck cmn.Bck, objNameOrTmpl string) (objName, listObjs, tmplObjs string, err error) { +// +// - handle (obj names) list, template (range), embedded prefix, and single object name +// - possibly call list-objects (via `lsObjVsPref`) to disambiguate +// + +type ( + // disambiguate objname vs prefix + dop struct { + isObj bool + isPref bool + notFound bool + } + // parsing result + oltp struct { + objName string + list string + tmpl string + notFound bool + } +) + +func lsObjVsPref(bck cmn.Bck, oname string) (dop dop, _ error) { + msg := &apc.LsoMsg{Prefix: oname} + + // NOTE: never "cached" (apc.LsObjCached) + msg.SetFlag(apc.LsNameOnly) + msg.SetFlag(apc.LsNoRecursion) + lst, err := api.ListObjectsPage(apiBP, bck, msg, api.ListArgs{Limit: 32}) + + if err != nil { + return dop, V(err) + } + if len(lst.Entries) == 0 { + dop.isObj, dop.notFound = true, true + return dop, nil + } + + for _, en := range lst.Entries { + if en.Name == oname { + dop.isObj = true + break + } + } + dop.isPref = len(lst.Entries) > 1 || !dop.isObj + return dop, nil +} + +func dopOLTP(c *cli.Context, bck cmn.Bck, objNameOrTmpl string) (oltp oltp, err error) { var prefix string if flagIsSet(c, listFlag) { - listObjs = parseStrFlag(c, listFlag) + oltp.list = parseStrFlag(c, listFlag) } if flagIsSet(c, templateFlag) { - tmplObjs = parseStrFlag(c, templateFlag) + oltp.tmpl = parseStrFlag(c, templateFlag) } // when template is a "pure" prefix (use '--prefix' to disambiguate vs. objName) if flagIsSet(c, verbObjPrefixFlag) { prefix = parseStrFlag(c, verbObjPrefixFlag) - if tmplObjs != "" { + if oltp.tmpl != "" { err = incorrectUsageMsg(c, errFmtExclusive, qflprn(verbObjPrefixFlag), qflprn(templateFlag)) - return "", "", "", err + return oltp, err } - tmplObjs = prefix + oltp.tmpl = prefix } - if listObjs != "" && tmplObjs != "" { + if oltp.list != "" && oltp.tmpl != "" { err = incorrectUsageMsg(c, errFmtExclusive, qflprn(listFlag), qflprn(templateFlag)) - return "", "", "", err + return oltp, err + } + if objNameOrTmpl == "" { + return oltp, err } - if objNameOrTmpl != "" { + switch { + case oltp.list != "" || oltp.tmpl != "": + what := "object name or prefix" + if isPattern(objNameOrTmpl) { + what = "pattern or template" + } + err = fmt.Errorf("%s (%s) cannot be used together with flags %s and %s (tip: use one or the other)", + what, objNameOrTmpl, qflprn(listFlag), qflprn(templateFlag)) + return oltp, err + case isPattern(objNameOrTmpl): + oltp.tmpl = objNameOrTmpl + case flagIsSet(c, noRecursFlag): + oltp.objName = objNameOrTmpl + + default: + // [NOTE] additional list-objects call to disambiguate: differentiate embedded prefix from object name + dop, err := lsObjVsPref(bck, objNameOrTmpl) + oltp.notFound = dop.notFound switch { - case listObjs != "" || tmplObjs != "": - what := "object name or prefix" - if isPattern(objNameOrTmpl) { - what = "pattern or template" - } - err = fmt.Errorf("%s (%s) cannot be used together with flags %s and %s (tip: use one or the other)", - what, objNameOrTmpl, qflprn(listFlag), qflprn(templateFlag)) - return "", "", "", err - case isPattern(objNameOrTmpl): - tmplObjs = objNameOrTmpl - case flagIsSet(c, noRecursFlag): - objName = objNameOrTmpl - - default: - // [NOTE] additional list-objects call to disambiguate: differentiate embedded prefix from object name - dop, err := lsObjVsPref(bck, objNameOrTmpl) - switch { - case err != nil: - return "", "", "", err - case dop.isObj && dop.isPref: - err := fmt.Errorf("part of the URI %q can be interpreted as an object name and/or mutli-object matching prefix\n"+ - "(Tip: to disambiguate, use either %s or %s)", objNameOrTmpl, qflprn(noRecursFlag), qflprn(verbObjPrefixFlag)) - return "", "", "", err - case dop.isObj: - objName = objNameOrTmpl - case dop.isPref: - // (operation on all 'prefix'-ed objects) - tmplObjs, objName = objNameOrTmpl, "" - } + case err != nil: + return oltp, err + case dop.isObj && dop.isPref: + err := fmt.Errorf("part of the URI %q can be interpreted as an object name and/or mutli-object matching prefix\n"+ + "(Tip: to disambiguate, use either %s or %s)", objNameOrTmpl, qflprn(noRecursFlag), qflprn(verbObjPrefixFlag)) + return oltp, err + case dop.isObj: + oltp.objName = objNameOrTmpl + case dop.isPref: + // (operation on all 'prefix'-ed objects) + oltp.tmpl, oltp.objName = objNameOrTmpl, "" } } - return objName, listObjs, tmplObjs, err + + return oltp, err } diff --git a/cmd/cli/cli/tcbtco.go b/cmd/cli/cli/tcbtco.go index 1389035dc1..7359fed65f 100644 --- a/cmd/cli/cli/tcbtco.go +++ b/cmd/cli/cli/tcbtco.go @@ -98,12 +98,12 @@ func copyTransform(c *cli.Context, etlName, objNameOrTmpl string, bckFrom, bckTo return nil } - objName, listObjs, tmplObjs, err := parseObjListTemplate(c, bckFrom, objNameOrTmpl) + oltp, err := dopOLTP(c, bckFrom, objNameOrTmpl) if err != nil { return err } - // HEAD(to) + // bck-to exists? if _, err = api.HeadBucket(apiBP, bckTo, true /* don't add */); err != nil { if herr, ok := err.(*cmn.ErrHTTP); !ok || herr.Status != http.StatusNotFound { return err @@ -115,8 +115,10 @@ func copyTransform(c *cli.Context, etlName, objNameOrTmpl string, bckFrom, bckTo dryRun := flagIsSet(c, copyDryRunFlag) - // either 1. copy/transform bucket (x-tcb) - if objName == "" && listObjs == "" && tmplObjs == "" { + // + // either (1) copy/transform bucket (x-tcb) + // + if oltp.objName == "" && oltp.list == "" && oltp.tmpl == "" { // NOTE: e.g. 'ais cp gs://abc gs:/abc' to sync remote bucket => aistore if bckFrom.Equal(&bckTo) && !bckFrom.IsRemote() { return incorrectUsageMsg(c, errFmtSameBucket, commandCopy, bckTo) @@ -132,21 +134,23 @@ func copyTransform(c *cli.Context, etlName, objNameOrTmpl string, bckFrom, bckTo return copyBucket(c, bckFrom, bckTo) } - // or 2. multi-object x-tco - if listObjs == "" && tmplObjs == "" { - listObjs = objName // NOTE: "pure" prefix comment in parseObjListTemplate (above) + // + // or (2) multi-object x-tco + // + if oltp.list == "" && oltp.tmpl == "" { + oltp.list = oltp.objName // (compare with `_prefetchOne`) } if dryRun { var prompt string - if listObjs != "" { - prompt = fmt.Sprintf("%s %q ...\n", text2, listObjs) + if oltp.list != "" { + prompt = fmt.Sprintf("%s %q ...\n", text2, oltp.list) } else { - prompt = fmt.Sprintf("%s objects that match the pattern %q ...\n", text2, tmplObjs) + prompt = fmt.Sprintf("%s objects that match the pattern %q ...\n", text2, oltp.tmpl) } dryRunCptn(c) // TODO: ditto actionDone(c, prompt) } - return runTCO(c, bckFrom, bckTo, listObjs, tmplObjs, etlName) + return runTCO(c, bckFrom, bckTo, oltp.list, oltp.tmpl, etlName) } func _iniCopyBckMsg(c *cli.Context, msg *apc.CopyBckMsg) (err error) { diff --git a/cmd/cli/cli/utils.go b/cmd/cli/cli/utils.go index 7453c35262..2e9a016daa 100644 --- a/cmd/cli/cli/utils.go +++ b/cmd/cli/cli/utils.go @@ -98,26 +98,6 @@ func arg0Node(c *cli.Context) (node *meta.Snode, sname string, err error) { return } -// -// misplaced or mistyped flag(s) -// - -func errArgIsFlag(c *cli.Context, arg string) (err error) { - if len(arg) > 1 && arg[0] == '-' { - err = incorrectUsageMsg(c, "missing command line argument (hint: flag '%s' misplaced?)", arg) - } - return err -} - -func errTailArgsContainFlag(tail []string) error { - for _, arg := range tail { - if len(arg) > 1 && arg[0] == '-' { - return fmt.Errorf("unrecognized or misplaced option %q", arg) - } - } - return nil -} - func reorderTailArgs(left string, middle []string, right ...string) string { var sb strings.Builder sb.WriteString(left) @@ -676,37 +656,6 @@ func isBucketEmpty(bck cmn.Bck, cached bool) (bool, error) { return len(objList.Entries) == 0, nil } -// disambiguate objname vs prefix -type disambiguateObjPref struct { - isObj, isPref bool -} - -func lsObjVsPref(bck cmn.Bck, oname string) (dop disambiguateObjPref, _ error) { - msg := &apc.LsoMsg{Prefix: oname} - - // NOTE: never "cached" (apc.LsObjCached) - msg.SetFlag(apc.LsNameOnly) - msg.SetFlag(apc.LsNoRecursion) - lst, err := api.ListObjectsPage(apiBP, bck, msg, api.ListArgs{Limit: 32}) - - if err != nil { - return dop, V(err) - } - if len(lst.Entries) == 0 { - dop.isObj = true // caller to further deal with it - return dop, nil - } - - for _, en := range lst.Entries { - if en.Name == oname { - dop.isObj = true - break - } - } - dop.isPref = len(lst.Entries) > 1 || !dop.isObj - return dop, nil -} - func ensureRemoteProvider(bck cmn.Bck) error { if !apc.IsProvider(bck.Provider) { return fmt.Errorf("invalid bucket %q: missing backend provider", bck) diff --git a/cmd/cli/cli/yap.go b/cmd/cli/cli/yap.go index f8169f0d67..4019543c20 100644 --- a/cmd/cli/cli/yap.go +++ b/cmd/cli/cli/yap.go @@ -221,17 +221,18 @@ func (a *archbck) parse(c *cli.Context) (err error) { if a.rsrc.bck, objNameOrTmpl, err = parseBckObjURI(c, uri, true /*emptyObjnameOK*/); err != nil { return err } - objName, listObjs, tmplObjs, err := parseObjListTemplate(c, a.rsrc.bck, objNameOrTmpl) + + oltp, err := dopOLTP(c, a.rsrc.bck, objNameOrTmpl) if err != nil { return err } - if listObjs == "" && tmplObjs == "" { - listObjs = objName // NOTE: "pure" prefix comment in parseObjListTemplate (above) + if oltp.list == "" && oltp.tmpl == "" { + oltp.list = oltp.objName // (compare with `_prefetchOne`, `copyTransform`) } - if listObjs != "" { - a.rsrc.lr.ObjNames = splitCsv(listObjs) + if oltp.list != "" { + a.rsrc.lr.ObjNames = splitCsv(oltp.list) } else { - a.rsrc.lr.Template = tmplObjs + a.rsrc.lr.Template = oltp.tmpl } return }