package object import ( "fmt" "strconv" "strings" internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "github.com/spf13/cobra" ) const ( newAttrsFlagName = "new-attrs" replaceAttrsFlagName = "replace-attrs" rangeFlagName = "range" payloadFlagName = "payload" ) var objectPatchCmd = &cobra.Command{ Use: "patch", Run: patch, Short: "Patch FrostFS object", Long: "Patch FrostFS object. Each range passed to the command requires to pass a corresponding patch payload.", Example: ` frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid --oid --new-attrs 'key1=val1,key2=val2' --replace-attrs frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid --oid --range offX:lnX --payload /path/to/payloadX --range offY:lnY --payload /path/to/payloadY frostfs-cli -c config.yml -r 127.0.0.1:8080 object patch --cid --oid --new-attrs 'key1=val1,key2=val2' --replace-attrs --range offX:lnX --payload /path/to/payload `, } func initObjectPatchCmd() { commonflags.Init(objectPatchCmd) initFlagSession(objectPatchCmd, "PATCH") flags := objectPatchCmd.Flags() flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage) _ = objectRangeCmd.MarkFlagRequired(commonflags.CIDFlag) flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage) _ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag) flags.String(newAttrsFlagName, "", "New object attributes in form of Key1=Value1,Key2=Value2") flags.Bool(replaceAttrsFlagName, false, "Replace object attributes by new ones.") flags.StringSlice(rangeFlagName, []string{}, "Range to which patch payload is applied. Format: offset:length") flags.StringSlice(payloadFlagName, []string{}, "Path to file with patch payload.") } func patch(cmd *cobra.Command, _ []string) { var cnr cid.ID var obj oid.ID objAddr := readObjectAddress(cmd, &cnr, &obj) ranges, err := getRangeSlice(cmd) commonCmd.ExitOnErr(cmd, "", err) payloads := patchPayloadPaths(cmd) if len(ranges) != len(payloads) { commonCmd.ExitOnErr(cmd, "", fmt.Errorf("the number of ranges and payloads are not equal: ranges = %d, payloads = %d", len(ranges), len(payloads))) } newAttrs, err := parseNewObjectAttrs(cmd) commonCmd.ExitOnErr(cmd, "can't parse new object attributes: %w", err) replaceAttrs, _ := cmd.Flags().GetBool(replaceAttrsFlagName) pk := key.GetOrGenerate(cmd) cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC) var prm internalclient.PatchObjectPrm prm.SetClient(cli) Prepare(cmd, &prm) ReadOrOpenSession(cmd, &prm, pk, cnr, nil) prm.SetAddress(objAddr) prm.NewAttributes = newAttrs prm.ReplaceAttribute = replaceAttrs for i := range ranges { prm.PayloadPatches = append(prm.PayloadPatches, internalclient.PayloadPatch{ Range: ranges[i], PayloadPath: payloads[i], }) } res, err := internalclient.Patch(cmd.Context(), prm) if err != nil { commonCmd.ExitOnErr(cmd, "can't patch the object: %w", err) } cmd.Println("Patched object ID: ", res.OID.EncodeToString()) } func parseNewObjectAttrs(cmd *cobra.Command) ([]objectSDK.Attribute, error) { var rawAttrs []string raw := cmd.Flag(newAttrsFlagName).Value.String() if len(raw) != 0 { rawAttrs = strings.Split(raw, ",") } attrs := make([]objectSDK.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes for i := range rawAttrs { k, v, found := strings.Cut(rawAttrs[i], "=") if !found { return nil, fmt.Errorf("invalid attribute format: %s", rawAttrs[i]) } attrs[i].SetKey(k) attrs[i].SetValue(v) } return attrs, nil } func getRangeSlice(cmd *cobra.Command) ([]objectSDK.Range, error) { v, _ := cmd.Flags().GetStringSlice(rangeFlagName) if len(v) == 0 { return []objectSDK.Range{}, nil } rs := make([]objectSDK.Range, len(v)) for i := range v { before, after, found := strings.Cut(v[i], rangeSep) if !found { return nil, fmt.Errorf("invalid range specifier: %s", v[i]) } offset, err := strconv.ParseUint(before, 10, 64) if err != nil { return nil, fmt.Errorf("invalid '%s' range offset specifier: %w", v[i], err) } length, err := strconv.ParseUint(after, 10, 64) if err != nil { return nil, fmt.Errorf("invalid '%s' range length specifier: %w", v[i], err) } rs[i].SetOffset(offset) rs[i].SetLength(length) } return rs, nil } func patchPayloadPaths(cmd *cobra.Command) []string { v, _ := cmd.Flags().GetStringSlice(payloadFlagName) return v }