From 46952afe3741f3409025beb6586c6164605917f1 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Wed, 3 Jul 2019 11:59:14 +1000 Subject: [PATCH 1/3] feat(influxd): New influxd verify tsm-blocks command This command performs verification of TSM blocks * expected and actual CRC-32 checksums match * expected and actual min and max timestamps match decoded data --- cmd/influxd/main.go | 2 + cmd/influxd/verify/tsm_blocks.go | 147 +++++++++++++++++++++++++++++++ cmd/influxd/verify/verify.go | 25 ++++++ kit/cli/flags.go | 40 +++++++++ tsdb/explode.go | 8 ++ 5 files changed, 222 insertions(+) create mode 100644 cmd/influxd/verify/tsm_blocks.go create mode 100644 cmd/influxd/verify/verify.go create mode 100644 kit/cli/flags.go diff --git a/cmd/influxd/main.go b/cmd/influxd/main.go index 1dd51a5520..6238e9543f 100644 --- a/cmd/influxd/main.go +++ b/cmd/influxd/main.go @@ -9,6 +9,7 @@ import ( "github.com/influxdata/influxdb/cmd/influxd/generate" "github.com/influxdata/influxdb/cmd/influxd/inspect" "github.com/influxdata/influxdb/cmd/influxd/launcher" + "github.com/influxdata/influxdb/cmd/influxd/verify" _ "github.com/influxdata/influxdb/query/builtin" _ "github.com/influxdata/influxdb/tsdb/tsi1" _ "github.com/influxdata/influxdb/tsdb/tsm1" @@ -37,6 +38,7 @@ func init() { rootCmd.AddCommand(launcher.NewCommand()) rootCmd.AddCommand(generate.Command) rootCmd.AddCommand(inspect.NewCommand()) + rootCmd.AddCommand(verify.NewCommand()) } // find determines the default behavior when running influxd. diff --git a/cmd/influxd/verify/tsm_blocks.go b/cmd/influxd/verify/tsm_blocks.go new file mode 100644 index 0000000000..7b6bfe07f9 --- /dev/null +++ b/cmd/influxd/verify/tsm_blocks.go @@ -0,0 +1,147 @@ +package verify + +import ( + "bytes" + "fmt" + "hash/crc32" + "os" + "path/filepath" + + "github.com/influxdata/influxdb/kit/cli" + "github.com/influxdata/influxdb/tsdb" + "github.com/influxdata/influxdb/tsdb/cursors" + "github.com/influxdata/influxdb/tsdb/tsm1" + "github.com/spf13/cobra" +) + +// tsmBlocksFlags defines the `tsm-blocks` Command. +var tsmBlocksFlags = struct { + cli.OrgBucket + path string +}{} + +func newTSMBlocksCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tsm-blocks ...", + Short: "Verifies consistency of TSM blocks", + Long: ` +This command will analyze a set of TSM files for inconsistencies between the +TSM index and the blocks. + +The checks performed by this command are: + +* CRC-32 checksums match for each block +* TSM index min and max timestamps match decoded data + +OPTIONS + + ... + A list of files or directories to search for TSM files. + +An optional organization or organization and bucket may be specified to limit +the analysis. +`, + Run: verifyTSMBlocks, + } + + tsmBlocksFlags.AddFlags(cmd) + + return cmd +} + +func verifyTSMBlocks(cmd *cobra.Command, args []string) { + for _, arg := range args { + fi, err := os.Stat(arg) + if err != nil { + fmt.Printf("Error processing path %q: %v", arg, err) + continue + } + + var files []string + if fi.IsDir() { + files, _ = filepath.Glob(filepath.Join(arg, "*."+tsm1.TSMFileExtension)) + } else { + files = append(files, arg) + } + for _, path := range files { + if err := processFile(path); err != nil { + fmt.Printf("Error processing file %q: %v", path, err) + } + } + } +} + +func processFile(path string) error { + fmt.Println("processing file: " + path) + + file, err := os.OpenFile(path, os.O_RDONLY, 0600) + if err != nil { + return fmt.Errorf("OpenFile: %v", err) + } + + reader, err := tsm1.NewTSMReader(file) + if err != nil { + return fmt.Errorf("failed to create TSM reader for %q: %v", path, err) + } + defer reader.Close() + + org, bucket := tsmBlocksFlags.OrgBucketID() + var start []byte + if org.Valid() { + if bucket.Valid() { + v := tsdb.EncodeName(org, bucket) + start = v[:] + } else { + v := tsdb.EncodeOrgName(org) + start = v[:] + } + } + + var ts cursors.TimestampArray + count := 0 + totalErrors := 0 + iter := reader.Iterator(start) + for iter.Next() { + key := iter.Key() + if len(start) > 0 && (len(key) < len(start) || !bytes.Equal(key[:len(start)], start)) { + break + } + + entries := iter.Entries() + for i := range entries { + entry := &entries[i] + + checksum, buf, err := reader.ReadBytes(entry, nil) + if err != nil { + fmt.Printf("could not read block %d due to error: %q\n", count, err) + count++ + continue + } + + if expected := crc32.ChecksumIEEE(buf); checksum != expected { + totalErrors++ + fmt.Printf("unexpected checksum %d, expected %d for key %v, block %d\n", checksum, expected, key, count) + } + + if err = tsm1.DecodeTimestampArrayBlock(buf, &ts); err != nil { + totalErrors++ + fmt.Printf("unable to decode timestamps for block %d: %q\n", count, err) + } + + if got, exp := entry.MinTime, ts.MinTime(); got != exp { + totalErrors++ + fmt.Printf("unexpected min time %d, expected %d for block %d: %q\n", got, exp, count, err) + } + if got, exp := entry.MaxTime, ts.MaxTime(); got != exp { + totalErrors++ + fmt.Printf("unexpected max time %d, expected %d for block %d: %q\n", got, exp, count, err) + } + + count++ + } + } + + fmt.Printf("Completed checking %d block(s)\n", count) + + return nil +} diff --git a/cmd/influxd/verify/verify.go b/cmd/influxd/verify/verify.go new file mode 100644 index 0000000000..2fb59ba9b7 --- /dev/null +++ b/cmd/influxd/verify/verify.go @@ -0,0 +1,25 @@ +package verify + +import ( + "github.com/spf13/cobra" +) + +// NewCommand creates the new command. +func NewCommand() *cobra.Command { + base := &cobra.Command{ + Use: "verify", + Short: "Commands for verifying on-disk database data", + } + + // List of available sub-commands + // If a new sub-command is created, it must be added here + subCommands := []*cobra.Command{ + newTSMBlocksCommand(), + } + + for _, command := range subCommands { + base.AddCommand(command) + } + + return base +} diff --git a/kit/cli/flags.go b/kit/cli/flags.go new file mode 100644 index 0000000000..d19349b520 --- /dev/null +++ b/kit/cli/flags.go @@ -0,0 +1,40 @@ +package cli + +import ( + "github.com/influxdata/influxdb" + "github.com/influxdata/influxdb/tsdb" + "github.com/spf13/cobra" +) + +// The ID type can be used to +type ID struct { + influxdb.ID +} + +// Set parses the hex string s and sets the value of i. +func (i *ID) Set(v string) error { + return i.Decode([]byte(v)) +} + +func (i *ID) Type() string { + return "ID" +} + +type OrgBucket struct { + Org ID + Bucket ID +} + +func (o *OrgBucket) AddFlags(cmd *cobra.Command) { + flagSet := cmd.Flags() + flagSet.Var(&o.Org, "org-id", "organization id") + flagSet.Var(&o.Bucket, "bucket-id", "bucket id") +} + +func (o *OrgBucket) OrgBucketID() (orgID, bucketID influxdb.ID) { + return o.Org.ID, o.Bucket.ID +} + +func (o *OrgBucket) Name() [influxdb.IDLength]byte { + return tsdb.EncodeName(o.OrgBucketID()) +} diff --git a/tsdb/explode.go b/tsdb/explode.go index a4182fe06b..e380fe1943 100644 --- a/tsdb/explode.go +++ b/tsdb/explode.go @@ -27,6 +27,14 @@ func EncodeName(org, bucket platform.ID) [16]byte { return nameBytes } +// EncodeOrgName converts org to the tsdb internal serialization that may be used +// as a prefix when searching for keys matching a specific organization. +func EncodeOrgName(org platform.ID) [8]byte { + var orgBytes [8]byte + binary.BigEndian.PutUint64(orgBytes[0:8], uint64(org)) + return orgBytes +} + // EncodeNameString converts org/bucket pairs to the tsdb internal serialization func EncodeNameString(org, bucket platform.ID) string { name := EncodeName(org, bucket) From 00561d5a1b2b0b595e642a69f21471700903ebc0 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Thu, 4 Jul 2019 09:30:29 +1000 Subject: [PATCH 2/3] feedback: Move verify routines to `tsm1` package for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Should have left it there to begin with 🤣 --- cmd/influxd/inspect/inspect.go | 1 + cmd/influxd/inspect/verify_tsm.go | 72 +++++++++++++++ cmd/influxd/main.go | 2 - cmd/influxd/verify/tsm_blocks.go | 147 ------------------------------ cmd/influxd/verify/verify.go | 25 ----- tsdb/tsm1/verify_tsm.go | 103 +++++++++++++++++++++ 6 files changed, 176 insertions(+), 174 deletions(-) create mode 100644 cmd/influxd/inspect/verify_tsm.go delete mode 100644 cmd/influxd/verify/tsm_blocks.go delete mode 100644 cmd/influxd/verify/verify.go create mode 100644 tsdb/tsm1/verify_tsm.go diff --git a/cmd/influxd/inspect/inspect.go b/cmd/influxd/inspect/inspect.go index 01a91a2cf8..0a53d7c889 100644 --- a/cmd/influxd/inspect/inspect.go +++ b/cmd/influxd/inspect/inspect.go @@ -16,6 +16,7 @@ func NewCommand() *cobra.Command { subCommands := []*cobra.Command{ NewExportBlocksCommand(), NewReportTSMCommand(), + NewVerifyTSMCommand(), NewVerifyWALCommand(), } diff --git a/cmd/influxd/inspect/verify_tsm.go b/cmd/influxd/inspect/verify_tsm.go new file mode 100644 index 0000000000..e9fa76cdba --- /dev/null +++ b/cmd/influxd/inspect/verify_tsm.go @@ -0,0 +1,72 @@ +package inspect + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/influxdata/influxdb/kit/cli" + "github.com/influxdata/influxdb/tsdb/tsm1" + "github.com/spf13/cobra" +) + +// verifyTSMFlags defines the `verify-tsm` Command. +var verifyTSMFlags = struct { + cli.OrgBucket + path string +}{} + +func NewVerifyTSMCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "verify-tsm ...", + Short: "Checks the consistency of TSM files", + Long: ` +This command will analyze a set of TSM files for inconsistencies between the +TSM index and the blocks. + +The checks performed by this command are: + +* CRC-32 checksums match for each block +* TSM index min and max timestamps match decoded data + +OPTIONS + + ... + A list of files or directories to search for TSM files. + +An optional organization or organization and bucket may be specified to limit +the analysis. +`, + RunE: verifyTSMF, + } + + verifyTSMFlags.AddFlags(cmd) + + return cmd +} + +func verifyTSMF(cmd *cobra.Command, args []string) error { + verify := tsm1.VerifyTSM{ + Stdout: os.Stdout, + OrgID: verifyTSMFlags.Org.ID, + BucketID: verifyTSMFlags.Bucket.ID, + } + + // resolve all pathspecs + for _, arg := range args { + fi, err := os.Stat(arg) + if err != nil { + fmt.Printf("Error processing path %q: %v", arg, err) + continue + } + + if fi.IsDir() { + files, _ := filepath.Glob(filepath.Join(arg, "*."+tsm1.TSMFileExtension)) + verify.Paths = append(verify.Paths, files...) + } else { + verify.Paths = append(verify.Paths, arg) + } + } + + return verify.Run() +} diff --git a/cmd/influxd/main.go b/cmd/influxd/main.go index 6238e9543f..1dd51a5520 100644 --- a/cmd/influxd/main.go +++ b/cmd/influxd/main.go @@ -9,7 +9,6 @@ import ( "github.com/influxdata/influxdb/cmd/influxd/generate" "github.com/influxdata/influxdb/cmd/influxd/inspect" "github.com/influxdata/influxdb/cmd/influxd/launcher" - "github.com/influxdata/influxdb/cmd/influxd/verify" _ "github.com/influxdata/influxdb/query/builtin" _ "github.com/influxdata/influxdb/tsdb/tsi1" _ "github.com/influxdata/influxdb/tsdb/tsm1" @@ -38,7 +37,6 @@ func init() { rootCmd.AddCommand(launcher.NewCommand()) rootCmd.AddCommand(generate.Command) rootCmd.AddCommand(inspect.NewCommand()) - rootCmd.AddCommand(verify.NewCommand()) } // find determines the default behavior when running influxd. diff --git a/cmd/influxd/verify/tsm_blocks.go b/cmd/influxd/verify/tsm_blocks.go deleted file mode 100644 index 7b6bfe07f9..0000000000 --- a/cmd/influxd/verify/tsm_blocks.go +++ /dev/null @@ -1,147 +0,0 @@ -package verify - -import ( - "bytes" - "fmt" - "hash/crc32" - "os" - "path/filepath" - - "github.com/influxdata/influxdb/kit/cli" - "github.com/influxdata/influxdb/tsdb" - "github.com/influxdata/influxdb/tsdb/cursors" - "github.com/influxdata/influxdb/tsdb/tsm1" - "github.com/spf13/cobra" -) - -// tsmBlocksFlags defines the `tsm-blocks` Command. -var tsmBlocksFlags = struct { - cli.OrgBucket - path string -}{} - -func newTSMBlocksCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "tsm-blocks ...", - Short: "Verifies consistency of TSM blocks", - Long: ` -This command will analyze a set of TSM files for inconsistencies between the -TSM index and the blocks. - -The checks performed by this command are: - -* CRC-32 checksums match for each block -* TSM index min and max timestamps match decoded data - -OPTIONS - - ... - A list of files or directories to search for TSM files. - -An optional organization or organization and bucket may be specified to limit -the analysis. -`, - Run: verifyTSMBlocks, - } - - tsmBlocksFlags.AddFlags(cmd) - - return cmd -} - -func verifyTSMBlocks(cmd *cobra.Command, args []string) { - for _, arg := range args { - fi, err := os.Stat(arg) - if err != nil { - fmt.Printf("Error processing path %q: %v", arg, err) - continue - } - - var files []string - if fi.IsDir() { - files, _ = filepath.Glob(filepath.Join(arg, "*."+tsm1.TSMFileExtension)) - } else { - files = append(files, arg) - } - for _, path := range files { - if err := processFile(path); err != nil { - fmt.Printf("Error processing file %q: %v", path, err) - } - } - } -} - -func processFile(path string) error { - fmt.Println("processing file: " + path) - - file, err := os.OpenFile(path, os.O_RDONLY, 0600) - if err != nil { - return fmt.Errorf("OpenFile: %v", err) - } - - reader, err := tsm1.NewTSMReader(file) - if err != nil { - return fmt.Errorf("failed to create TSM reader for %q: %v", path, err) - } - defer reader.Close() - - org, bucket := tsmBlocksFlags.OrgBucketID() - var start []byte - if org.Valid() { - if bucket.Valid() { - v := tsdb.EncodeName(org, bucket) - start = v[:] - } else { - v := tsdb.EncodeOrgName(org) - start = v[:] - } - } - - var ts cursors.TimestampArray - count := 0 - totalErrors := 0 - iter := reader.Iterator(start) - for iter.Next() { - key := iter.Key() - if len(start) > 0 && (len(key) < len(start) || !bytes.Equal(key[:len(start)], start)) { - break - } - - entries := iter.Entries() - for i := range entries { - entry := &entries[i] - - checksum, buf, err := reader.ReadBytes(entry, nil) - if err != nil { - fmt.Printf("could not read block %d due to error: %q\n", count, err) - count++ - continue - } - - if expected := crc32.ChecksumIEEE(buf); checksum != expected { - totalErrors++ - fmt.Printf("unexpected checksum %d, expected %d for key %v, block %d\n", checksum, expected, key, count) - } - - if err = tsm1.DecodeTimestampArrayBlock(buf, &ts); err != nil { - totalErrors++ - fmt.Printf("unable to decode timestamps for block %d: %q\n", count, err) - } - - if got, exp := entry.MinTime, ts.MinTime(); got != exp { - totalErrors++ - fmt.Printf("unexpected min time %d, expected %d for block %d: %q\n", got, exp, count, err) - } - if got, exp := entry.MaxTime, ts.MaxTime(); got != exp { - totalErrors++ - fmt.Printf("unexpected max time %d, expected %d for block %d: %q\n", got, exp, count, err) - } - - count++ - } - } - - fmt.Printf("Completed checking %d block(s)\n", count) - - return nil -} diff --git a/cmd/influxd/verify/verify.go b/cmd/influxd/verify/verify.go deleted file mode 100644 index 2fb59ba9b7..0000000000 --- a/cmd/influxd/verify/verify.go +++ /dev/null @@ -1,25 +0,0 @@ -package verify - -import ( - "github.com/spf13/cobra" -) - -// NewCommand creates the new command. -func NewCommand() *cobra.Command { - base := &cobra.Command{ - Use: "verify", - Short: "Commands for verifying on-disk database data", - } - - // List of available sub-commands - // If a new sub-command is created, it must be added here - subCommands := []*cobra.Command{ - newTSMBlocksCommand(), - } - - for _, command := range subCommands { - base.AddCommand(command) - } - - return base -} diff --git a/tsdb/tsm1/verify_tsm.go b/tsdb/tsm1/verify_tsm.go new file mode 100644 index 0000000000..82b362a16c --- /dev/null +++ b/tsdb/tsm1/verify_tsm.go @@ -0,0 +1,103 @@ +package tsm1 + +import ( + "bytes" + "fmt" + "hash/crc32" + "io" + "os" + + "github.com/influxdata/influxdb" + "github.com/influxdata/influxdb/tsdb" + "github.com/influxdata/influxdb/tsdb/cursors" +) + +type VerifyTSM struct { + Stdout io.Writer + Paths []string + OrgID influxdb.ID + BucketID influxdb.ID +} + +func (v *VerifyTSM) Run() error { + for _, path := range v.Paths { + if err := v.processFile(path); err != nil { + fmt.Fprintf(v.Stdout, "Error processing file %q: %v", path, err) + } + } + return nil +} + +func (v *VerifyTSM) processFile(path string) error { + fmt.Println("processing file: " + path) + + file, err := os.OpenFile(path, os.O_RDONLY, 0600) + if err != nil { + return fmt.Errorf("OpenFile: %v", err) + } + + reader, err := NewTSMReader(file) + if err != nil { + return fmt.Errorf("failed to create TSM reader for %q: %v", path, err) + } + defer reader.Close() + + var start []byte + if v.OrgID.Valid() { + if v.BucketID.Valid() { + v := tsdb.EncodeName(v.OrgID, v.BucketID) + start = v[:] + } else { + v := tsdb.EncodeOrgName(v.OrgID) + start = v[:] + } + } + + var ts cursors.TimestampArray + count := 0 + totalErrors := 0 + iter := reader.Iterator(start) + for iter.Next() { + key := iter.Key() + if len(start) > 0 && (len(key) < len(start) || !bytes.Equal(key[:len(start)], start)) { + break + } + + entries := iter.Entries() + for i := range entries { + entry := &entries[i] + + checksum, buf, err := reader.ReadBytes(entry, nil) + if err != nil { + fmt.Fprintf(v.Stdout, "could not read block %d due to error: %q\n", count, err) + count++ + continue + } + + if expected := crc32.ChecksumIEEE(buf); checksum != expected { + totalErrors++ + fmt.Fprintf(v.Stdout, "unexpected checksum %d, expected %d for key %v, block %d\n", checksum, expected, key, count) + } + + if err = DecodeTimestampArrayBlock(buf, &ts); err != nil { + totalErrors++ + fmt.Fprintf(v.Stdout, "unable to decode timestamps for block %d: %q\n", count, err) + } + + if got, exp := entry.MinTime, ts.MinTime(); got != exp { + totalErrors++ + fmt.Fprintf(v.Stdout, "unexpected min time %d, expected %d for block %d: %q\n", got, exp, count, err) + } + if got, exp := entry.MaxTime, ts.MaxTime(); got != exp { + totalErrors++ + fmt.Fprintf(v.Stdout, "unexpected max time %d, expected %d for block %d: %q\n", got, exp, count, err) + } + + count++ + } + } + + fmt.Fprintf(v.Stdout, "Completed checking %d block(s)\n", count) + + return nil +} From 030e11a82a4ff67b8f4f07e633882ba7f10f8d67 Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Tue, 9 Jul 2019 09:20:02 +1000 Subject: [PATCH 3/3] feat: Allow existing influxdb.ID type to be used as a pflag --- cmd/influxd/inspect/verify_tsm.go | 4 +-- kit/cli/flags.go | 40 --------------------- kit/cli/idflag.go | 60 +++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 42 deletions(-) delete mode 100644 kit/cli/flags.go create mode 100644 kit/cli/idflag.go diff --git a/cmd/influxd/inspect/verify_tsm.go b/cmd/influxd/inspect/verify_tsm.go index e9fa76cdba..49fe287390 100644 --- a/cmd/influxd/inspect/verify_tsm.go +++ b/cmd/influxd/inspect/verify_tsm.go @@ -48,8 +48,8 @@ the analysis. func verifyTSMF(cmd *cobra.Command, args []string) error { verify := tsm1.VerifyTSM{ Stdout: os.Stdout, - OrgID: verifyTSMFlags.Org.ID, - BucketID: verifyTSMFlags.Bucket.ID, + OrgID: verifyTSMFlags.Org, + BucketID: verifyTSMFlags.Bucket, } // resolve all pathspecs diff --git a/kit/cli/flags.go b/kit/cli/flags.go deleted file mode 100644 index d19349b520..0000000000 --- a/kit/cli/flags.go +++ /dev/null @@ -1,40 +0,0 @@ -package cli - -import ( - "github.com/influxdata/influxdb" - "github.com/influxdata/influxdb/tsdb" - "github.com/spf13/cobra" -) - -// The ID type can be used to -type ID struct { - influxdb.ID -} - -// Set parses the hex string s and sets the value of i. -func (i *ID) Set(v string) error { - return i.Decode([]byte(v)) -} - -func (i *ID) Type() string { - return "ID" -} - -type OrgBucket struct { - Org ID - Bucket ID -} - -func (o *OrgBucket) AddFlags(cmd *cobra.Command) { - flagSet := cmd.Flags() - flagSet.Var(&o.Org, "org-id", "organization id") - flagSet.Var(&o.Bucket, "bucket-id", "bucket id") -} - -func (o *OrgBucket) OrgBucketID() (orgID, bucketID influxdb.ID) { - return o.Org.ID, o.Bucket.ID -} - -func (o *OrgBucket) Name() [influxdb.IDLength]byte { - return tsdb.EncodeName(o.OrgBucketID()) -} diff --git a/kit/cli/idflag.go b/kit/cli/idflag.go new file mode 100644 index 0000000000..ab7b851f77 --- /dev/null +++ b/kit/cli/idflag.go @@ -0,0 +1,60 @@ +package cli + +import ( + "github.com/influxdata/influxdb" + "github.com/influxdata/influxdb/tsdb" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// Wrapper for influxdb.ID +type idValue influxdb.ID + +func newIDValue(val influxdb.ID, p *influxdb.ID) *idValue { + *p = val + return (*idValue)(p) +} + +func (i *idValue) String() string { return influxdb.ID(*i).String() } +func (i *idValue) Set(s string) error { + id, err := influxdb.IDFromString(s) + if err != nil { + return err + } + *i = idValue(*id) + return nil +} + +func (i *idValue) Type() string { + return "ID" +} + +// IDVar defines an influxdb.ID flag with specified name, default value, and usage string. +// The argument p points to an influxdb.ID variable in which to store the value of the flag. +func IDVar(fs *pflag.FlagSet, p *influxdb.ID, name string, value influxdb.ID, usage string) { + IDVarP(fs, p, name, "", value, usage) +} + +// IDVarP is like IDVar, but accepts a shorthand letter that can be used after a single dash. +func IDVarP(fs *pflag.FlagSet, p *influxdb.ID, name, shorthand string, value influxdb.ID, usage string) { + fs.VarP(newIDValue(value, p), name, shorthand, usage) +} + +type OrgBucket struct { + Org influxdb.ID + Bucket influxdb.ID +} + +func (o *OrgBucket) AddFlags(cmd *cobra.Command) { + fs := cmd.Flags() + IDVar(fs, &o.Org, "org-id", influxdb.InvalidID(), "organization id") + IDVar(fs, &o.Bucket, "bucket-id", influxdb.InvalidID(), "bucket id") +} + +func (o *OrgBucket) OrgBucketID() (orgID, bucketID influxdb.ID) { + return o.Org, o.Bucket +} + +func (o *OrgBucket) Name() [influxdb.IDLength]byte { + return tsdb.EncodeName(o.OrgBucketID()) +}