You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1426 lines
39 KiB

  1. /*
  2. Copyright 2015 Google LLC
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. // Command docs are in cbtdoc.go.
  15. import (
  16. "bytes"
  17. "flag"
  18. "fmt"
  19. "go/format"
  20. "io"
  21. "log"
  22. "os"
  23. "regexp"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "text/tabwriter"
  28. "text/template"
  29. "time"
  30. "encoding/csv"
  31. "cloud.google.com/go/bigtable"
  32. "cloud.google.com/go/bigtable/internal/cbtconfig"
  33. "golang.org/x/net/context"
  34. "google.golang.org/api/iterator"
  35. "google.golang.org/api/option"
  36. "google.golang.org/grpc"
  37. )
  38. var (
  39. oFlag = flag.String("o", "", "if set, redirect stdout to this file")
  40. config *cbtconfig.Config
  41. client *bigtable.Client
  42. adminClient *bigtable.AdminClient
  43. instanceAdminClient *bigtable.InstanceAdminClient
  44. version = "<unknown version>"
  45. revision = "<unknown revision>"
  46. revisionDate = "<unknown revision date>"
  47. cliUserAgent = "cbt-cli-go/unknown"
  48. )
  49. func getCredentialOpts(opts []option.ClientOption) []option.ClientOption {
  50. if ts := config.TokenSource; ts != nil {
  51. opts = append(opts, option.WithTokenSource(ts))
  52. }
  53. if tlsCreds := config.TLSCreds; tlsCreds != nil {
  54. opts = append(opts, option.WithGRPCDialOption(grpc.WithTransportCredentials(tlsCreds)))
  55. }
  56. return opts
  57. }
  58. func getClient(clientConf bigtable.ClientConfig) *bigtable.Client {
  59. if client == nil {
  60. var opts []option.ClientOption
  61. if ep := config.DataEndpoint; ep != "" {
  62. opts = append(opts, option.WithEndpoint(ep))
  63. }
  64. opts = append(opts, option.WithUserAgent(cliUserAgent))
  65. opts = getCredentialOpts(opts)
  66. var err error
  67. client, err = bigtable.NewClientWithConfig(context.Background(), config.Project, config.Instance, clientConf, opts...)
  68. if err != nil {
  69. log.Fatalf("Making bigtable.Client: %v", err)
  70. }
  71. opts = append(opts, option.WithUserAgent(cliUserAgent))
  72. }
  73. return client
  74. }
  75. func getAdminClient() *bigtable.AdminClient {
  76. if adminClient == nil {
  77. var opts []option.ClientOption
  78. if ep := config.AdminEndpoint; ep != "" {
  79. opts = append(opts, option.WithEndpoint(ep))
  80. }
  81. opts = append(opts, option.WithUserAgent(cliUserAgent))
  82. opts = getCredentialOpts(opts)
  83. var err error
  84. adminClient, err = bigtable.NewAdminClient(context.Background(), config.Project, config.Instance, opts...)
  85. if err != nil {
  86. log.Fatalf("Making bigtable.AdminClient: %v", err)
  87. }
  88. }
  89. return adminClient
  90. }
  91. func getInstanceAdminClient() *bigtable.InstanceAdminClient {
  92. if instanceAdminClient == nil {
  93. var opts []option.ClientOption
  94. if ep := config.AdminEndpoint; ep != "" {
  95. opts = append(opts, option.WithEndpoint(ep))
  96. }
  97. opts = getCredentialOpts(opts)
  98. var err error
  99. instanceAdminClient, err = bigtable.NewInstanceAdminClient(context.Background(), config.Project, opts...)
  100. if err != nil {
  101. log.Fatalf("Making bigtable.InstanceAdminClient: %v", err)
  102. }
  103. }
  104. return instanceAdminClient
  105. }
  106. func main() {
  107. var err error
  108. config, err = cbtconfig.Load()
  109. if err != nil {
  110. log.Fatal(err)
  111. }
  112. config.RegisterFlags()
  113. flag.Usage = func() { usage(os.Stderr) }
  114. flag.Parse()
  115. if flag.NArg() == 0 {
  116. usage(os.Stderr)
  117. os.Exit(1)
  118. }
  119. if *oFlag != "" {
  120. f, err := os.Create(*oFlag)
  121. if err != nil {
  122. log.Fatal(err)
  123. }
  124. defer func() {
  125. if err := f.Close(); err != nil {
  126. log.Fatal(err)
  127. }
  128. }()
  129. os.Stdout = f
  130. }
  131. if config.UserAgent != "" {
  132. cliUserAgent = config.UserAgent
  133. }
  134. ctx := context.Background()
  135. for _, cmd := range commands {
  136. if cmd.Name == flag.Arg(0) {
  137. if err := config.CheckFlags(cmd.Required); err != nil {
  138. log.Fatal(err)
  139. }
  140. cmd.do(ctx, flag.Args()[1:]...)
  141. return
  142. }
  143. }
  144. log.Fatalf("Unknown command %q", flag.Arg(0))
  145. }
  146. func usage(w io.Writer) {
  147. fmt.Fprintf(w, "Usage: %s [flags] <command> ...\n", os.Args[0])
  148. flag.CommandLine.SetOutput(w)
  149. flag.CommandLine.PrintDefaults()
  150. fmt.Fprintf(w, "\n%s", cmdSummary)
  151. }
  152. var cmdSummary string // generated in init, below
  153. func init() {
  154. var buf bytes.Buffer
  155. tw := tabwriter.NewWriter(&buf, 10, 8, 4, '\t', 0)
  156. for _, cmd := range commands {
  157. fmt.Fprintf(tw, "cbt %s\t%s\n", cmd.Name, cmd.Desc)
  158. }
  159. tw.Flush()
  160. buf.WriteString(configHelp)
  161. buf.WriteString("\ncbt " + version + " " + revision + " " + revisionDate + "\n")
  162. cmdSummary = buf.String()
  163. }
  164. var configHelp = `
  165. Alpha features are not currently available to most Cloud Bigtable customers. The
  166. features might be changed in backward-incompatible ways and are not recommended
  167. for production use. They are not subject to any SLA or deprecation policy.
  168. For convenience, values of the -project, -instance, -creds,
  169. -admin-endpoint and -data-endpoint flags may be specified in
  170. ~/.cbtrc in this format:
  171. project = my-project-123
  172. instance = my-instance
  173. creds = path-to-account-key.json
  174. admin-endpoint = hostname:port
  175. data-endpoint = hostname:port
  176. All values are optional, and all will be overridden by flags.
  177. `
  178. var commands = []struct {
  179. Name, Desc string
  180. do func(context.Context, ...string)
  181. Usage string
  182. Required cbtconfig.RequiredFlags
  183. }{
  184. {
  185. Name: "count",
  186. Desc: "Count rows in a table",
  187. do: doCount,
  188. Usage: "cbt count <table>",
  189. Required: cbtconfig.ProjectAndInstanceRequired,
  190. },
  191. {
  192. Name: "createinstance",
  193. Desc: "Create an instance with an initial cluster",
  194. do: doCreateInstance,
  195. Usage: "cbt createinstance <instance-id> <display-name> <cluster-id> <zone> <num-nodes> <storage type>\n" +
  196. " instance-id Permanent, unique id for the instance\n" +
  197. " display-name Description of the instance\n" +
  198. " cluster-id Permanent, unique id for the cluster in the instance\n" +
  199. " zone The zone in which to create the cluster\n" +
  200. " num-nodes The number of nodes to create\n" +
  201. " storage-type SSD or HDD\n",
  202. Required: cbtconfig.ProjectRequired,
  203. },
  204. {
  205. Name: "createcluster",
  206. Desc: "Create a cluster in the configured instance (replication alpha)",
  207. do: doCreateCluster,
  208. Usage: "cbt createcluster <cluster-id> <zone> <num-nodes> <storage type>\n" +
  209. " cluster-id Permanent, unique id for the cluster in the instance\n" +
  210. " zone The zone in which to create the cluster\n" +
  211. " num-nodes The number of nodes to create\n" +
  212. " storage-type SSD or HDD\n",
  213. Required: cbtconfig.ProjectAndInstanceRequired,
  214. },
  215. {
  216. Name: "createfamily",
  217. Desc: "Create a column family",
  218. do: doCreateFamily,
  219. Usage: "cbt createfamily <table> <family>",
  220. Required: cbtconfig.ProjectAndInstanceRequired,
  221. },
  222. {
  223. Name: "createtable",
  224. Desc: "Create a table",
  225. do: doCreateTable,
  226. Usage: "cbt createtable <table> [families=family[:(maxage=<d> | maxversions=<n>)],...] [splits=split,...]\n" +
  227. " families: Column families and their associated GC policies. See \"setgcpolicy\".\n" +
  228. " Example: families=family1:maxage=1w,family2:maxversions=1\n" +
  229. " splits: Row key to be used to initially split the table",
  230. Required: cbtconfig.ProjectAndInstanceRequired,
  231. },
  232. {
  233. Name: "updatecluster",
  234. Desc: "Update a cluster in the configured instance",
  235. do: doUpdateCluster,
  236. Usage: "cbt updatecluster <cluster-id> [num-nodes=num-nodes]\n" +
  237. " cluster-id Permanent, unique id for the cluster in the instance\n" +
  238. " num-nodes The number of nodes to update to",
  239. Required: cbtconfig.ProjectAndInstanceRequired,
  240. },
  241. {
  242. Name: "deleteinstance",
  243. Desc: "Delete an instance",
  244. do: doDeleteInstance,
  245. Usage: "cbt deleteinstance <instance>",
  246. Required: cbtconfig.ProjectRequired,
  247. },
  248. {
  249. Name: "deletecluster",
  250. Desc: "Delete a cluster from the configured instance (replication alpha)",
  251. do: doDeleteCluster,
  252. Usage: "cbt deletecluster <cluster>",
  253. Required: cbtconfig.ProjectAndInstanceRequired,
  254. },
  255. {
  256. Name: "deletecolumn",
  257. Desc: "Delete all cells in a column",
  258. do: doDeleteColumn,
  259. Usage: "cbt deletecolumn <table> <row> <family> <column> [app-profile=<app profile id>]\n" +
  260. " app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
  261. Required: cbtconfig.ProjectAndInstanceRequired,
  262. },
  263. {
  264. Name: "deletefamily",
  265. Desc: "Delete a column family",
  266. do: doDeleteFamily,
  267. Usage: "cbt deletefamily <table> <family>",
  268. Required: cbtconfig.ProjectAndInstanceRequired,
  269. },
  270. {
  271. Name: "deleterow",
  272. Desc: "Delete a row",
  273. do: doDeleteRow,
  274. Usage: "cbt deleterow <table> <row> [app-profile=<app profile id>]\n" +
  275. " app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
  276. Required: cbtconfig.ProjectAndInstanceRequired,
  277. },
  278. {
  279. Name: "deletetable",
  280. Desc: "Delete a table",
  281. do: doDeleteTable,
  282. Usage: "cbt deletetable <table>",
  283. Required: cbtconfig.ProjectAndInstanceRequired,
  284. },
  285. {
  286. Name: "doc",
  287. Desc: "Print godoc-suitable documentation for cbt",
  288. do: doDoc,
  289. Usage: "cbt doc",
  290. Required: cbtconfig.NoneRequired,
  291. },
  292. {
  293. Name: "help",
  294. Desc: "Print help text",
  295. do: doHelp,
  296. Usage: "cbt help [command]",
  297. Required: cbtconfig.NoneRequired,
  298. },
  299. {
  300. Name: "listinstances",
  301. Desc: "List instances in a project",
  302. do: doListInstances,
  303. Usage: "cbt listinstances",
  304. Required: cbtconfig.ProjectRequired,
  305. },
  306. {
  307. Name: "listclusters",
  308. Desc: "List clusters in an instance",
  309. do: doListClusters,
  310. Usage: "cbt listclusters",
  311. Required: cbtconfig.ProjectAndInstanceRequired,
  312. },
  313. {
  314. Name: "lookup",
  315. Desc: "Read from a single row",
  316. do: doLookup,
  317. Usage: "cbt lookup <table> <row> [columns=[family]:[qualifier],...] [cells-per-column=<n>] " +
  318. "[app-profile=<app profile id>]\n" +
  319. " columns=[family]:[qualifier],... Read only these columns, comma-separated\n" +
  320. " cells-per-column=<n> Read only this many cells per column\n" +
  321. " app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
  322. Required: cbtconfig.ProjectAndInstanceRequired,
  323. },
  324. {
  325. Name: "ls",
  326. Desc: "List tables and column families",
  327. do: doLS,
  328. Usage: "cbt ls List tables\n" +
  329. "cbt ls <table> List column families in <table>",
  330. Required: cbtconfig.ProjectAndInstanceRequired,
  331. },
  332. {
  333. Name: "mddoc",
  334. Desc: "Print documentation for cbt in Markdown format",
  335. do: doMDDoc,
  336. Usage: "cbt mddoc",
  337. Required: cbtconfig.NoneRequired,
  338. },
  339. {
  340. Name: "read",
  341. Desc: "Read rows",
  342. do: doRead,
  343. Usage: "cbt read <table> [start=<row>] [end=<row>] [prefix=<prefix>]" +
  344. " [regex=<regex>] [columns=[family]:[qualifier],...] [count=<n>] [cells-per-column=<n>]" +
  345. " [app-profile=<app profile id>]\n" +
  346. " start=<row> Start reading at this row\n" +
  347. " end=<row> Stop reading before this row\n" +
  348. " prefix=<prefix> Read rows with this prefix\n" +
  349. " regex=<regex> Read rows with keys matching this regex\n" +
  350. " columns=[family]:[qualifier],... Read only these columns, comma-separated\n" +
  351. " count=<n> Read only this many rows\n" +
  352. " cells-per-column=<n> Read only this many cells per column\n" +
  353. " app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n",
  354. Required: cbtconfig.ProjectAndInstanceRequired,
  355. },
  356. {
  357. Name: "set",
  358. Desc: "Set value of a cell",
  359. do: doSet,
  360. Usage: "cbt set <table> <row> [app-profile=<app profile id>] family:column=val[@ts] ...\n" +
  361. " app-profile=<app profile id> The app profile id to use for the request (replication alpha)\n" +
  362. " family:column=val[@ts] may be repeated to set multiple cells.\n" +
  363. "\n" +
  364. " ts is an optional integer timestamp.\n" +
  365. " If it cannot be parsed, the `@ts` part will be\n" +
  366. " interpreted as part of the value.",
  367. Required: cbtconfig.ProjectAndInstanceRequired,
  368. },
  369. {
  370. Name: "setgcpolicy",
  371. Desc: "Set the GC policy for a column family",
  372. do: doSetGCPolicy,
  373. Usage: "cbt setgcpolicy <table> <family> ( maxage=<d> | maxversions=<n> )\n" +
  374. "\n" +
  375. ` maxage=<d> Maximum timestamp age to preserve (e.g. "1h", "4d")` + "\n" +
  376. " maxversions=<n> Maximum number of versions to preserve",
  377. Required: cbtconfig.ProjectAndInstanceRequired,
  378. },
  379. {
  380. Name: "waitforreplication",
  381. Desc: "Block until all the completed writes have been replicated to all the clusters (replication alpha)",
  382. do: doWaitForReplicaiton,
  383. Usage: "cbt waitforreplication <table>",
  384. Required: cbtconfig.ProjectAndInstanceRequired,
  385. },
  386. {
  387. Name: "createtablefromsnapshot",
  388. Desc: "Create a table from a snapshot (snapshots alpha)",
  389. do: doCreateTableFromSnapshot,
  390. Usage: "cbt createtablefromsnapshot <table> <cluster> <snapshot>\n" +
  391. " table The name of the table to create\n" +
  392. " cluster The cluster where the snapshot is located\n" +
  393. " snapshot The snapshot to restore",
  394. Required: cbtconfig.ProjectAndInstanceRequired,
  395. },
  396. {
  397. Name: "createsnapshot",
  398. Desc: "Create a snapshot from a source table (snapshots alpha)",
  399. do: doSnapshotTable,
  400. Usage: "cbt createsnapshot <cluster> <snapshot> <table> [ttl=<d>]\n" +
  401. "\n" +
  402. ` [ttl=<d>] Lifespan of the snapshot (e.g. "1h", "4d")` + "\n",
  403. Required: cbtconfig.ProjectAndInstanceRequired,
  404. },
  405. {
  406. Name: "listsnapshots",
  407. Desc: "List snapshots in a cluster (snapshots alpha)",
  408. do: doListSnapshots,
  409. Usage: "cbt listsnapshots [<cluster>]",
  410. Required: cbtconfig.ProjectAndInstanceRequired,
  411. },
  412. {
  413. Name: "getsnapshot",
  414. Desc: "Get snapshot info (snapshots alpha)",
  415. do: doGetSnapshot,
  416. Usage: "cbt getsnapshot <cluster> <snapshot>",
  417. Required: cbtconfig.ProjectAndInstanceRequired,
  418. },
  419. {
  420. Name: "deletesnapshot",
  421. Desc: "Delete snapshot in a cluster (snapshots alpha)",
  422. do: doDeleteSnapshot,
  423. Usage: "cbt deletesnapshot <cluster> <snapshot>",
  424. Required: cbtconfig.ProjectAndInstanceRequired,
  425. },
  426. {
  427. Name: "version",
  428. Desc: "Print the current cbt version",
  429. do: doVersion,
  430. Usage: "cbt version",
  431. Required: cbtconfig.NoneRequired,
  432. },
  433. }
  434. func doCount(ctx context.Context, args ...string) {
  435. if len(args) != 1 {
  436. log.Fatal("usage: cbt count <table>")
  437. }
  438. tbl := getClient(bigtable.ClientConfig{}).Open(args[0])
  439. n := 0
  440. err := tbl.ReadRows(ctx, bigtable.InfiniteRange(""), func(_ bigtable.Row) bool {
  441. n++
  442. return true
  443. }, bigtable.RowFilter(bigtable.StripValueFilter()))
  444. if err != nil {
  445. log.Fatalf("Reading rows: %v", err)
  446. }
  447. fmt.Println(n)
  448. }
  449. func doCreateTable(ctx context.Context, args ...string) {
  450. if len(args) < 1 {
  451. log.Fatal("usage: cbt createtable <table> [families=family[:gcpolicy],...] [splits=split,...]")
  452. }
  453. tblConf := bigtable.TableConf{TableID: args[0]}
  454. parsed, err := parseArgs(args[1:], []string{"families", "splits"})
  455. if err != nil {
  456. log.Fatal(err)
  457. }
  458. for key, val := range parsed {
  459. chunks, err := csv.NewReader(strings.NewReader(val)).Read()
  460. if err != nil {
  461. log.Fatalf("Invalid %s arg format: %v", key, err)
  462. }
  463. switch key {
  464. case "families":
  465. tblConf.Families = make(map[string]bigtable.GCPolicy)
  466. for _, family := range chunks {
  467. famPolicy := strings.Split(family, ":")
  468. var gcPolicy bigtable.GCPolicy
  469. if len(famPolicy) < 2 {
  470. gcPolicy = bigtable.MaxVersionsPolicy(1)
  471. log.Printf("Using default GC Policy of %v for family %v", gcPolicy, family)
  472. } else {
  473. gcPolicy, err = parseGCPolicy(famPolicy[1])
  474. if err != nil {
  475. log.Fatal(err)
  476. }
  477. }
  478. tblConf.Families[famPolicy[0]] = gcPolicy
  479. }
  480. case "splits":
  481. tblConf.SplitKeys = chunks
  482. }
  483. }
  484. if err := getAdminClient().CreateTableFromConf(ctx, &tblConf); err != nil {
  485. log.Fatalf("Creating table: %v", err)
  486. }
  487. }
  488. func doCreateFamily(ctx context.Context, args ...string) {
  489. if len(args) != 2 {
  490. log.Fatal("usage: cbt createfamily <table> <family>")
  491. }
  492. err := getAdminClient().CreateColumnFamily(ctx, args[0], args[1])
  493. if err != nil {
  494. log.Fatalf("Creating column family: %v", err)
  495. }
  496. }
  497. func doCreateInstance(ctx context.Context, args ...string) {
  498. if len(args) < 6 {
  499. log.Fatal("cbt createinstance <instance-id> <display-name> <cluster-id> <zone> <num-nodes> <storage type>")
  500. }
  501. numNodes, err := strconv.ParseInt(args[4], 0, 32)
  502. if err != nil {
  503. log.Fatalf("Bad num-nodes %q: %v", args[4], err)
  504. }
  505. sType, err := parseStorageType(args[5])
  506. if err != nil {
  507. log.Fatal(err)
  508. }
  509. ic := bigtable.InstanceWithClustersConfig{
  510. InstanceID: args[0],
  511. DisplayName: args[1],
  512. Clusters: []bigtable.ClusterConfig{{
  513. ClusterID: args[2],
  514. Zone: args[3],
  515. NumNodes: int32(numNodes),
  516. StorageType: sType,
  517. }},
  518. }
  519. err = getInstanceAdminClient().CreateInstanceWithClusters(ctx, &ic)
  520. if err != nil {
  521. log.Fatalf("Creating instance: %v", err)
  522. }
  523. }
  524. func doCreateCluster(ctx context.Context, args ...string) {
  525. if len(args) < 4 {
  526. log.Fatal("usage: cbt createcluster <cluster-id> <zone> <num-nodes> <storage type>")
  527. }
  528. numNodes, err := strconv.ParseInt(args[2], 0, 32)
  529. if err != nil {
  530. log.Fatalf("Bad num_nodes %q: %v", args[2], err)
  531. }
  532. sType, err := parseStorageType(args[3])
  533. if err != nil {
  534. log.Fatal(err)
  535. }
  536. cc := bigtable.ClusterConfig{
  537. InstanceID: config.Instance,
  538. ClusterID: args[0],
  539. Zone: args[1],
  540. NumNodes: int32(numNodes),
  541. StorageType: sType,
  542. }
  543. err = getInstanceAdminClient().CreateCluster(ctx, &cc)
  544. if err != nil {
  545. log.Fatalf("Creating cluster: %v", err)
  546. }
  547. }
  548. func doUpdateCluster(ctx context.Context, args ...string) {
  549. if len(args) < 2 {
  550. log.Fatal("cbt updatecluster <cluster-id> [num-nodes=num-nodes]")
  551. }
  552. numNodes := int64(0)
  553. parsed, err := parseArgs(args[1:], []string{"num-nodes"})
  554. if err != nil {
  555. log.Fatal(err)
  556. }
  557. if val, ok := parsed["num-nodes"]; ok {
  558. numNodes, err = strconv.ParseInt(val, 0, 32)
  559. if err != nil {
  560. log.Fatalf("Bad num-nodes %q: %v", val, err)
  561. }
  562. }
  563. if numNodes > 0 {
  564. err = getInstanceAdminClient().UpdateCluster(ctx, config.Instance, args[0], int32(numNodes))
  565. if err != nil {
  566. log.Fatalf("Updating cluster: %v", err)
  567. }
  568. } else {
  569. log.Fatal("Updating cluster: nothing to update")
  570. }
  571. }
  572. func doDeleteInstance(ctx context.Context, args ...string) {
  573. if len(args) != 1 {
  574. log.Fatal("usage: cbt deleteinstance <instance>")
  575. }
  576. err := getInstanceAdminClient().DeleteInstance(ctx, args[0])
  577. if err != nil {
  578. log.Fatalf("Deleting instance: %v", err)
  579. }
  580. }
  581. func doDeleteCluster(ctx context.Context, args ...string) {
  582. if len(args) != 1 {
  583. log.Fatal("usage: cbt deletecluster <cluster>")
  584. }
  585. err := getInstanceAdminClient().DeleteCluster(ctx, config.Instance, args[0])
  586. if err != nil {
  587. log.Fatalf("Deleting cluster: %v", err)
  588. }
  589. }
  590. func doDeleteColumn(ctx context.Context, args ...string) {
  591. usage := "usage: cbt deletecolumn <table> <row> <family> <column> [app-profile=<app profile id>]"
  592. if len(args) != 4 && len(args) != 5 {
  593. log.Fatal(usage)
  594. }
  595. var appProfile string
  596. if len(args) == 5 {
  597. if !strings.HasPrefix(args[4], "app-profile=") {
  598. log.Fatal(usage)
  599. }
  600. appProfile = strings.Split(args[4], "=")[1]
  601. }
  602. tbl := getClient(bigtable.ClientConfig{AppProfile: appProfile}).Open(args[0])
  603. mut := bigtable.NewMutation()
  604. mut.DeleteCellsInColumn(args[2], args[3])
  605. if err := tbl.Apply(ctx, args[1], mut); err != nil {
  606. log.Fatalf("Deleting cells in column: %v", err)
  607. }
  608. }
  609. func doDeleteFamily(ctx context.Context, args ...string) {
  610. if len(args) != 2 {
  611. log.Fatal("usage: cbt deletefamily <table> <family>")
  612. }
  613. err := getAdminClient().DeleteColumnFamily(ctx, args[0], args[1])
  614. if err != nil {
  615. log.Fatalf("Deleting column family: %v", err)
  616. }
  617. }
  618. func doDeleteRow(ctx context.Context, args ...string) {
  619. usage := "usage: cbt deleterow <table> <row> [app-profile=<app profile id>]"
  620. if len(args) != 2 && len(args) != 3 {
  621. log.Fatal(usage)
  622. }
  623. var appProfile string
  624. if len(args) == 3 {
  625. if !strings.HasPrefix(args[2], "app-profile=") {
  626. log.Fatal(usage)
  627. }
  628. appProfile = strings.Split(args[2], "=")[1]
  629. }
  630. tbl := getClient(bigtable.ClientConfig{AppProfile: appProfile}).Open(args[0])
  631. mut := bigtable.NewMutation()
  632. mut.DeleteRow()
  633. if err := tbl.Apply(ctx, args[1], mut); err != nil {
  634. log.Fatalf("Deleting row: %v", err)
  635. }
  636. }
  637. func doDeleteTable(ctx context.Context, args ...string) {
  638. if len(args) != 1 {
  639. log.Fatalf("Can't do `cbt deletetable %s`", args)
  640. }
  641. err := getAdminClient().DeleteTable(ctx, args[0])
  642. if err != nil {
  643. log.Fatalf("Deleting table: %v", err)
  644. }
  645. }
  646. // to break circular dependencies
  647. var (
  648. doDocFn func(ctx context.Context, args ...string)
  649. doHelpFn func(ctx context.Context, args ...string)
  650. doMDDocFn func(ctx context.Context, args ...string)
  651. )
  652. func init() {
  653. doDocFn = doDocReal
  654. doHelpFn = doHelpReal
  655. doMDDocFn = doMDDocReal
  656. }
  657. func doDoc(ctx context.Context, args ...string) { doDocFn(ctx, args...) }
  658. func doHelp(ctx context.Context, args ...string) { doHelpFn(ctx, args...) }
  659. func doMDDoc(ctx context.Context, args ...string) { doMDDocFn(ctx, args...) }
  660. func docFlags() []*flag.Flag {
  661. // Only include specific flags, in a specific order.
  662. var flags []*flag.Flag
  663. for _, name := range []string{"project", "instance", "creds"} {
  664. f := flag.Lookup(name)
  665. if f == nil {
  666. log.Fatalf("Flag not linked: -%s", name)
  667. }
  668. flags = append(flags, f)
  669. }
  670. return flags
  671. }
  672. func doDocReal(ctx context.Context, args ...string) {
  673. data := map[string]interface{}{
  674. "Commands": commands,
  675. "Flags": docFlags(),
  676. "ConfigHelp": configHelp,
  677. }
  678. var buf bytes.Buffer
  679. if err := docTemplate.Execute(&buf, data); err != nil {
  680. log.Fatalf("Bad doc template: %v", err)
  681. }
  682. out, err := format.Source(buf.Bytes())
  683. if err != nil {
  684. log.Fatalf("Bad doc output: %v", err)
  685. }
  686. os.Stdout.Write(out)
  687. }
  688. func indentLines(s, ind string) string {
  689. ss := strings.Split(s, "\n")
  690. for i, p := range ss {
  691. ss[i] = ind + p
  692. }
  693. return strings.Join(ss, "\n")
  694. }
  695. var docTemplate = template.Must(template.New("doc").Funcs(template.FuncMap{
  696. "indent": indentLines,
  697. }).
  698. Parse(`
  699. // Copyright 2016 Google LLC
  700. //
  701. // Licensed under the Apache License, Version 2.0 (the "License");
  702. // you may not use this file except in compliance with the License.
  703. // You may obtain a copy of the License at
  704. //
  705. // http://www.apache.org/licenses/LICENSE-2.0
  706. //
  707. // Unless required by applicable law or agreed to in writing, software
  708. // distributed under the License is distributed on an "AS IS" BASIS,
  709. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  710. // See the License for the specific language governing permissions and
  711. // limitations under the License.
  712. // DO NOT EDIT. THIS IS AUTOMATICALLY GENERATED.
  713. // Run "go generate" to regenerate.
  714. //go:generate go run cbt.go -o cbtdoc.go doc
  715. /*
  716. Cbt is a tool for doing basic interactions with Cloud Bigtable. To learn how to
  717. install the cbt tool, see the
  718. [cbt overview](https://cloud.google.com/bigtable/docs/go/cbt-overview).
  719. Usage:
  720. cbt [options] command [arguments]
  721. The commands are:
  722. {{range .Commands}}
  723. {{printf "%-25s %s" .Name .Desc}}{{end}}
  724. Use "cbt help <command>" for more information about a command.
  725. The options are:
  726. {{range .Flags}}
  727. -{{.Name}} string
  728. {{.Usage}}{{end}}
  729. {{.ConfigHelp}}
  730. {{range .Commands}}
  731. {{.Desc}}
  732. Usage:
  733. {{indent .Usage "\t"}}
  734. {{end}}
  735. */
  736. package main
  737. `))
  738. func doHelpReal(ctx context.Context, args ...string) {
  739. if len(args) == 0 {
  740. usage(os.Stdout)
  741. return
  742. }
  743. for _, cmd := range commands {
  744. if cmd.Name == args[0] {
  745. fmt.Println(cmd.Usage)
  746. return
  747. }
  748. }
  749. log.Fatalf("Don't know command %q", args[0])
  750. }
  751. func doListInstances(ctx context.Context, args ...string) {
  752. if len(args) != 0 {
  753. log.Fatalf("usage: cbt listinstances")
  754. }
  755. is, err := getInstanceAdminClient().Instances(ctx)
  756. if err != nil {
  757. log.Fatalf("Getting list of instances: %v", err)
  758. }
  759. tw := tabwriter.NewWriter(os.Stdout, 10, 8, 4, '\t', 0)
  760. fmt.Fprintf(tw, "Instance Name\tInfo\n")
  761. fmt.Fprintf(tw, "-------------\t----\n")
  762. for _, i := range is {
  763. fmt.Fprintf(tw, "%s\t%s\n", i.Name, i.DisplayName)
  764. }
  765. tw.Flush()
  766. }
  767. func doListClusters(ctx context.Context, args ...string) {
  768. if len(args) != 0 {
  769. log.Fatalf("usage: cbt listclusters")
  770. }
  771. cis, err := getInstanceAdminClient().Clusters(ctx, config.Instance)
  772. if err != nil {
  773. log.Fatalf("Getting list of clusters: %v", err)
  774. }
  775. tw := tabwriter.NewWriter(os.Stdout, 10, 8, 4, '\t', 0)
  776. fmt.Fprintf(tw, "Cluster Name\tZone\tState\n")
  777. fmt.Fprintf(tw, "------------\t----\t----\n")
  778. for _, ci := range cis {
  779. fmt.Fprintf(tw, "%s\t%s\t%s (%d serve nodes)\n", ci.Name, ci.Zone, ci.State, ci.ServeNodes)
  780. }
  781. tw.Flush()
  782. }
  783. func doLookup(ctx context.Context, args ...string) {
  784. if len(args) < 2 {
  785. log.Fatalf("usage: cbt lookup <table> <row> [columns=<family:qualifier>...] [cells-per-column=<n>] " +
  786. "[app-profile=<app profile id>]")
  787. }
  788. parsed, err := parseArgs(args[2:], []string{"columns", "cells-per-column", "app-profile"})
  789. if err != nil {
  790. log.Fatal(err)
  791. }
  792. var opts []bigtable.ReadOption
  793. var filters []bigtable.Filter
  794. if cellsPerColumn := parsed["cells-per-column"]; cellsPerColumn != "" {
  795. n, err := strconv.Atoi(cellsPerColumn)
  796. if err != nil {
  797. log.Fatalf("Bad number of cells per column %q: %v", cellsPerColumn, err)
  798. }
  799. filters = append(filters, bigtable.LatestNFilter(n))
  800. }
  801. if columns := parsed["columns"]; columns != "" {
  802. columnFilters, err := parseColumnsFilter(columns)
  803. if err != nil {
  804. log.Fatal(err)
  805. }
  806. filters = append(filters, columnFilters)
  807. }
  808. if len(filters) > 1 {
  809. opts = append(opts, bigtable.RowFilter(bigtable.ChainFilters(filters...)))
  810. } else if len(filters) == 1 {
  811. opts = append(opts, bigtable.RowFilter(filters[0]))
  812. }
  813. table, row := args[0], args[1]
  814. tbl := getClient(bigtable.ClientConfig{AppProfile: parsed["app-profile"]}).Open(table)
  815. r, err := tbl.ReadRow(ctx, row, opts...)
  816. if err != nil {
  817. log.Fatalf("Reading row: %v", err)
  818. }
  819. printRow(r)
  820. }
  821. func printRow(r bigtable.Row) {
  822. fmt.Println(strings.Repeat("-", 40))
  823. fmt.Println(r.Key())
  824. var fams []string
  825. for fam := range r {
  826. fams = append(fams, fam)
  827. }
  828. sort.Strings(fams)
  829. for _, fam := range fams {
  830. ris := r[fam]
  831. sort.Sort(byColumn(ris))
  832. for _, ri := range ris {
  833. ts := time.Unix(0, int64(ri.Timestamp)*1e3)
  834. fmt.Printf(" %-40s @ %s\n", ri.Column, ts.Format("2006/01/02-15:04:05.000000"))
  835. fmt.Printf(" %q\n", ri.Value)
  836. }
  837. }
  838. }
  839. type byColumn []bigtable.ReadItem
  840. func (b byColumn) Len() int { return len(b) }
  841. func (b byColumn) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
  842. func (b byColumn) Less(i, j int) bool { return b[i].Column < b[j].Column }
  843. type byFamilyName []bigtable.FamilyInfo
  844. func (b byFamilyName) Len() int { return len(b) }
  845. func (b byFamilyName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
  846. func (b byFamilyName) Less(i, j int) bool { return b[i].Name < b[j].Name }
  847. func doLS(ctx context.Context, args ...string) {
  848. switch len(args) {
  849. default:
  850. log.Fatalf("Can't do `cbt ls %s`", args)
  851. case 0:
  852. tables, err := getAdminClient().Tables(ctx)
  853. if err != nil {
  854. log.Fatalf("Getting list of tables: %v", err)
  855. }
  856. sort.Strings(tables)
  857. for _, table := range tables {
  858. fmt.Println(table)
  859. }
  860. case 1:
  861. table := args[0]
  862. ti, err := getAdminClient().TableInfo(ctx, table)
  863. if err != nil {
  864. log.Fatalf("Getting table info: %v", err)
  865. }
  866. sort.Sort(byFamilyName(ti.FamilyInfos))
  867. tw := tabwriter.NewWriter(os.Stdout, 10, 8, 4, '\t', 0)
  868. fmt.Fprintf(tw, "Family Name\tGC Policy\n")
  869. fmt.Fprintf(tw, "-----------\t---------\n")
  870. for _, fam := range ti.FamilyInfos {
  871. fmt.Fprintf(tw, "%s\t%s\n", fam.Name, fam.GCPolicy)
  872. }
  873. tw.Flush()
  874. }
  875. }
  876. func doMDDocReal(ctx context.Context, args ...string) {
  877. data := map[string]interface{}{
  878. "Commands": commands,
  879. "Flags": docFlags(),
  880. "ConfigHelp": configHelp,
  881. }
  882. var buf bytes.Buffer
  883. if err := mddocTemplate.Execute(&buf, data); err != nil {
  884. log.Fatalf("Bad mddoc template: %v", err)
  885. }
  886. io.Copy(os.Stdout, &buf)
  887. }
  888. var mddocTemplate = template.Must(template.New("mddoc").Funcs(template.FuncMap{
  889. "indent": indentLines,
  890. }).
  891. Parse(`
  892. Cbt is a tool for doing basic interactions with Cloud Bigtable.
  893. Usage:
  894. cbt [options] command [arguments]
  895. The commands are:
  896. {{range .Commands}}
  897. {{printf "%-25s %s" .Name .Desc}}{{end}}
  898. Use "cbt help <command>" for more information about a command.
  899. The options are:
  900. {{range .Flags}}
  901. -{{.Name}} string
  902. {{.Usage}}{{end}}
  903. {{.ConfigHelp}}
  904. {{range .Commands}}
  905. ## {{.Desc}}
  906. {{indent .Usage "\t"}}
  907. {{end}}
  908. `))
  909. func doRead(ctx context.Context, args ...string) {
  910. if len(args) < 1 {
  911. log.Fatalf("usage: cbt read <table> [args ...]")
  912. }
  913. parsed, err := parseArgs(args[1:], []string{
  914. "start", "end", "prefix", "columns", "count", "cells-per-column", "regex", "app-profile", "limit",
  915. })
  916. if err != nil {
  917. log.Fatal(err)
  918. }
  919. if _, ok := parsed["limit"]; ok {
  920. // Be nicer; we used to support this, but renamed it to "end".
  921. log.Fatal("Unknown arg key 'limit'; did you mean 'end'?")
  922. }
  923. if (parsed["start"] != "" || parsed["end"] != "") && parsed["prefix"] != "" {
  924. log.Fatal(`"start"/"end" may not be mixed with "prefix"`)
  925. }
  926. var rr bigtable.RowRange
  927. if start, end := parsed["start"], parsed["end"]; end != "" {
  928. rr = bigtable.NewRange(start, end)
  929. } else if start != "" {
  930. rr = bigtable.InfiniteRange(start)
  931. }
  932. if prefix := parsed["prefix"]; prefix != "" {
  933. rr = bigtable.PrefixRange(prefix)
  934. }
  935. var opts []bigtable.ReadOption
  936. if count := parsed["count"]; count != "" {
  937. n, err := strconv.ParseInt(count, 0, 64)
  938. if err != nil {
  939. log.Fatalf("Bad count %q: %v", count, err)
  940. }
  941. opts = append(opts, bigtable.LimitRows(n))
  942. }
  943. var filters []bigtable.Filter
  944. if cellsPerColumn := parsed["cells-per-column"]; cellsPerColumn != "" {
  945. n, err := strconv.Atoi(cellsPerColumn)
  946. if err != nil {
  947. log.Fatalf("Bad number of cells per column %q: %v", cellsPerColumn, err)
  948. }
  949. filters = append(filters, bigtable.LatestNFilter(n))
  950. }
  951. if regex := parsed["regex"]; regex != "" {
  952. filters = append(filters, bigtable.RowKeyFilter(regex))
  953. }
  954. if columns := parsed["columns"]; columns != "" {
  955. columnFilters, err := parseColumnsFilter(columns)
  956. if err != nil {
  957. log.Fatal(err)
  958. }
  959. filters = append(filters, columnFilters)
  960. }
  961. if len(filters) > 1 {
  962. opts = append(opts, bigtable.RowFilter(bigtable.ChainFilters(filters...)))
  963. } else if len(filters) == 1 {
  964. opts = append(opts, bigtable.RowFilter(filters[0]))
  965. }
  966. // TODO(dsymonds): Support filters.
  967. tbl := getClient(bigtable.ClientConfig{AppProfile: parsed["app-profile"]}).Open(args[0])
  968. err = tbl.ReadRows(ctx, rr, func(r bigtable.Row) bool {
  969. printRow(r)
  970. return true
  971. }, opts...)
  972. if err != nil {
  973. log.Fatalf("Reading rows: %v", err)
  974. }
  975. }
  976. var setArg = regexp.MustCompile(`([^:]+):([^=]*)=(.*)`)
  977. func doSet(ctx context.Context, args ...string) {
  978. if len(args) < 3 {
  979. log.Fatalf("usage: cbt set <table> <row> [app-profile=<app profile id>] family:[column]=val[@ts] ...")
  980. }
  981. var appProfile string
  982. row := args[1]
  983. mut := bigtable.NewMutation()
  984. for _, arg := range args[2:] {
  985. if strings.HasPrefix(arg, "app-profile=") {
  986. appProfile = strings.Split(arg, "=")[1]
  987. continue
  988. }
  989. m := setArg.FindStringSubmatch(arg)
  990. if m == nil {
  991. log.Fatalf("Bad set arg %q", arg)
  992. }
  993. val := m[3]
  994. ts := bigtable.Now()
  995. if i := strings.LastIndex(val, "@"); i >= 0 {
  996. // Try parsing a timestamp.
  997. n, err := strconv.ParseInt(val[i+1:], 0, 64)
  998. if err == nil {
  999. val = val[:i]
  1000. ts = bigtable.Timestamp(n)
  1001. }
  1002. }
  1003. mut.Set(m[1], m[2], ts, []byte(val))
  1004. }
  1005. tbl := getClient(bigtable.ClientConfig{AppProfile: appProfile}).Open(args[0])
  1006. if err := tbl.Apply(ctx, row, mut); err != nil {
  1007. log.Fatalf("Applying mutation: %v", err)
  1008. }
  1009. }
  1010. func doSetGCPolicy(ctx context.Context, args ...string) {
  1011. if len(args) < 3 {
  1012. log.Fatalf("usage: cbt setgcpolicy <table> <family> ( maxage=<d> | maxversions=<n> | maxage=<d> (and|or) maxversions=<n> )")
  1013. }
  1014. table := args[0]
  1015. fam := args[1]
  1016. pol, err := parseGCPolicy(strings.Join(args[2:], " "))
  1017. if err != nil {
  1018. log.Fatal(err)
  1019. }
  1020. if err := getAdminClient().SetGCPolicy(ctx, table, fam, pol); err != nil {
  1021. log.Fatalf("Setting GC policy: %v", err)
  1022. }
  1023. }
  1024. func doWaitForReplicaiton(ctx context.Context, args ...string) {
  1025. if len(args) != 1 {
  1026. log.Fatalf("usage: cbt waitforreplication <table>")
  1027. }
  1028. table := args[0]
  1029. fmt.Printf("Waiting for all writes up to %s to be replicated.\n", time.Now().Format("2006/01/02-15:04:05"))
  1030. if err := getAdminClient().WaitForReplication(ctx, table); err != nil {
  1031. log.Fatalf("Waiting for replication: %v", err)
  1032. }
  1033. }
  1034. func parseGCPolicy(policyStr string) (bigtable.GCPolicy, error) {
  1035. words := strings.Fields(policyStr)
  1036. switch len(words) {
  1037. case 1:
  1038. return parseSinglePolicy(words[0])
  1039. case 3:
  1040. p1, err := parseSinglePolicy(words[0])
  1041. if err != nil {
  1042. return nil, err
  1043. }
  1044. p2, err := parseSinglePolicy(words[2])
  1045. if err != nil {
  1046. return nil, err
  1047. }
  1048. switch words[1] {
  1049. case "and":
  1050. return bigtable.IntersectionPolicy(p1, p2), nil
  1051. case "or":
  1052. return bigtable.UnionPolicy(p1, p2), nil
  1053. default:
  1054. return nil, fmt.Errorf("Expected 'and' or 'or', saw %q", words[1])
  1055. }
  1056. default:
  1057. return nil, fmt.Errorf("Expected '1' or '3' parameter count, saw %d", len(words))
  1058. }
  1059. return nil, nil
  1060. }
  1061. func parseSinglePolicy(s string) (bigtable.GCPolicy, error) {
  1062. words := strings.Split(s, "=")
  1063. if len(words) != 2 {
  1064. return nil, fmt.Errorf("Expected 'name=value', got %q", words)
  1065. }
  1066. switch words[0] {
  1067. case "maxage":
  1068. d, err := parseDuration(words[1])
  1069. if err != nil {
  1070. return nil, err
  1071. }
  1072. return bigtable.MaxAgePolicy(d), nil
  1073. case "maxversions":
  1074. n, err := strconv.ParseUint(words[1], 10, 16)
  1075. if err != nil {
  1076. return nil, err
  1077. }
  1078. return bigtable.MaxVersionsPolicy(int(n)), nil
  1079. default:
  1080. return nil, fmt.Errorf("Expected 'maxage' or 'maxversions', got %q", words[1])
  1081. }
  1082. return nil, nil
  1083. }
  1084. func parseStorageType(storageTypeStr string) (bigtable.StorageType, error) {
  1085. switch storageTypeStr {
  1086. case "SSD":
  1087. return bigtable.SSD, nil
  1088. case "HDD":
  1089. return bigtable.HDD, nil
  1090. }
  1091. return -1, fmt.Errorf("Invalid storage type: %v, must be SSD or HDD", storageTypeStr)
  1092. }
  1093. func doCreateTableFromSnapshot(ctx context.Context, args ...string) {
  1094. if len(args) != 3 {
  1095. log.Fatal("usage: cbt createtablefromsnapshot <table> <cluster> <snapshot>")
  1096. }
  1097. tableName := args[0]
  1098. clusterName := args[1]
  1099. snapshotName := args[2]
  1100. err := getAdminClient().CreateTableFromSnapshot(ctx, tableName, clusterName, snapshotName)
  1101. if err != nil {
  1102. log.Fatalf("Creating table: %v", err)
  1103. }
  1104. }
  1105. func doSnapshotTable(ctx context.Context, args ...string) {
  1106. if len(args) != 3 && len(args) != 4 {
  1107. log.Fatal("usage: cbt createsnapshot <cluster> <snapshot> <table> [ttl=<d>]")
  1108. }
  1109. clusterName := args[0]
  1110. snapshotName := args[1]
  1111. tableName := args[2]
  1112. ttl := bigtable.DefaultSnapshotDuration
  1113. parsed, err := parseArgs(args[3:], []string{"ttl"})
  1114. if err != nil {
  1115. log.Fatal(err)
  1116. }
  1117. if val, ok := parsed["ttl"]; ok {
  1118. var err error
  1119. ttl, err = parseDuration(val)
  1120. if err != nil {
  1121. log.Fatalf("Invalid snapshot ttl value %q: %v", val, err)
  1122. }
  1123. }
  1124. err = getAdminClient().SnapshotTable(ctx, tableName, clusterName, snapshotName, ttl)
  1125. if err != nil {
  1126. log.Fatalf("Failed to create Snapshot: %v", err)
  1127. }
  1128. }
  1129. func doListSnapshots(ctx context.Context, args ...string) {
  1130. if len(args) != 0 && len(args) != 1 {
  1131. log.Fatal("usage: cbt listsnapshots [<cluster>]")
  1132. }
  1133. var cluster string
  1134. if len(args) == 0 {
  1135. cluster = "-"
  1136. } else {
  1137. cluster = args[0]
  1138. }
  1139. it := getAdminClient().Snapshots(ctx, cluster)
  1140. tw := tabwriter.NewWriter(os.Stdout, 10, 8, 4, '\t', 0)
  1141. fmt.Fprintf(tw, "Snapshot\tSource Table\tCreated At\tExpires At\n")
  1142. fmt.Fprintf(tw, "--------\t------------\t----------\t----------\n")
  1143. timeLayout := "2006-01-02 15:04 MST"
  1144. for {
  1145. snapshot, err := it.Next()
  1146. if err == iterator.Done {
  1147. break
  1148. }
  1149. if err != nil {
  1150. log.Fatalf("Failed to fetch snapshots %v", err)
  1151. }
  1152. fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", snapshot.Name, snapshot.SourceTable, snapshot.CreateTime.Format(timeLayout), snapshot.DeleteTime.Format(timeLayout))
  1153. }
  1154. tw.Flush()
  1155. }
  1156. func doGetSnapshot(ctx context.Context, args ...string) {
  1157. if len(args) != 2 {
  1158. log.Fatalf("usage: cbt getsnapshot <cluster> <snapshot>")
  1159. }
  1160. clusterName := args[0]
  1161. snapshotName := args[1]
  1162. snapshot, err := getAdminClient().SnapshotInfo(ctx, clusterName, snapshotName)
  1163. if err != nil {
  1164. log.Fatalf("Failed to get snapshot: %v", err)
  1165. }
  1166. timeLayout := "2006-01-02 15:04 MST"
  1167. fmt.Printf("Name: %s\n", snapshot.Name)
  1168. fmt.Printf("Source table: %s\n", snapshot.SourceTable)
  1169. fmt.Printf("Created at: %s\n", snapshot.CreateTime.Format(timeLayout))
  1170. fmt.Printf("Expires at: %s\n", snapshot.DeleteTime.Format(timeLayout))
  1171. }
  1172. func doDeleteSnapshot(ctx context.Context, args ...string) {
  1173. if len(args) != 2 {
  1174. log.Fatal("usage: cbt deletesnapshot <cluster> <snapshot>")
  1175. }
  1176. cluster := args[0]
  1177. snapshot := args[1]
  1178. err := getAdminClient().DeleteSnapshot(ctx, cluster, snapshot)
  1179. if err != nil {
  1180. log.Fatalf("Failed to delete snapshot: %v", err)
  1181. }
  1182. }
  1183. // parseDuration parses a duration string.
  1184. // It is similar to Go's time.ParseDuration, except with a different set of supported units,
  1185. // and only simple formats supported.
  1186. func parseDuration(s string) (time.Duration, error) {
  1187. // [0-9]+[a-z]+
  1188. // Split [0-9]+ from [a-z]+.
  1189. i := 0
  1190. for ; i < len(s); i++ {
  1191. c := s[i]
  1192. if c < '0' || c > '9' {
  1193. break
  1194. }
  1195. }
  1196. ds, u := s[:i], s[i:]
  1197. if ds == "" || u == "" {
  1198. return 0, fmt.Errorf("invalid duration %q", s)
  1199. }
  1200. // Parse them.
  1201. d, err := strconv.ParseUint(ds, 10, 32)
  1202. if err != nil {
  1203. return 0, fmt.Errorf("invalid duration %q: %v", s, err)
  1204. }
  1205. unit, ok := unitMap[u]
  1206. if !ok {
  1207. return 0, fmt.Errorf("unknown unit %q in duration %q", u, s)
  1208. }
  1209. if d > uint64((1<<63-1)/unit) {
  1210. // overflow
  1211. return 0, fmt.Errorf("invalid duration %q overflows", s)
  1212. }
  1213. return time.Duration(d) * unit, nil
  1214. }
  1215. var unitMap = map[string]time.Duration{
  1216. "ms": time.Millisecond,
  1217. "s": time.Second,
  1218. "m": time.Minute,
  1219. "h": time.Hour,
  1220. "d": 24 * time.Hour,
  1221. }
  1222. func doVersion(ctx context.Context, args ...string) {
  1223. fmt.Printf("%s %s %s\n", version, revision, revisionDate)
  1224. }
  1225. // parseArgs takes a slice of arguments of the form key=value and returns a map from
  1226. // key to value. It returns an error if an argument is malformed or a key is not in
  1227. // the valid slice.
  1228. func parseArgs(args []string, valid []string) (map[string]string, error) {
  1229. parsed := make(map[string]string)
  1230. for _, arg := range args {
  1231. i := strings.Index(arg, "=")
  1232. if i < 0 {
  1233. return nil, fmt.Errorf("Bad arg %q", arg)
  1234. }
  1235. key, val := arg[:i], arg[i+1:]
  1236. if !stringInSlice(key, valid) {
  1237. return nil, fmt.Errorf("Unknown arg key %q", key)
  1238. }
  1239. parsed[key] = val
  1240. }
  1241. return parsed, nil
  1242. }
  1243. func stringInSlice(s string, list []string) bool {
  1244. for _, e := range list {
  1245. if s == e {
  1246. return true
  1247. }
  1248. }
  1249. return false
  1250. }
  1251. func parseColumnsFilter(columns string) (bigtable.Filter, error) {
  1252. splitColumns := strings.FieldsFunc(columns, func(c rune) bool { return c == ',' })
  1253. if len(splitColumns) == 1 {
  1254. filter, err := columnFilter(splitColumns[0])
  1255. if err != nil {
  1256. return nil, err
  1257. }
  1258. return filter, nil
  1259. } else {
  1260. var columnFilters []bigtable.Filter
  1261. for _, column := range splitColumns {
  1262. filter, err := columnFilter(column)
  1263. if err != nil {
  1264. return nil, err
  1265. }
  1266. columnFilters = append(columnFilters, filter)
  1267. }
  1268. return bigtable.InterleaveFilters(columnFilters...), nil
  1269. }
  1270. }
  1271. func columnFilter(column string) (bigtable.Filter, error) {
  1272. splitColumn := strings.Split(column, ":")
  1273. if len(splitColumn) == 1 {
  1274. return bigtable.ColumnFilter(splitColumn[0]), nil
  1275. } else if len(splitColumn) == 2 {
  1276. if strings.HasSuffix(column, ":") {
  1277. return bigtable.FamilyFilter(splitColumn[0]), nil
  1278. } else if strings.HasPrefix(column, ":") {
  1279. return bigtable.ColumnFilter(splitColumn[1]), nil
  1280. } else {
  1281. familyFilter := bigtable.FamilyFilter(splitColumn[0])
  1282. qualifierFilter := bigtable.ColumnFilter(splitColumn[1])
  1283. return bigtable.ChainFilters(familyFilter, qualifierFilter), nil
  1284. }
  1285. } else {
  1286. return nil, fmt.Errorf("Bad format for column %q", column)
  1287. }
  1288. }