Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd/influx): add secret cli #16786

Merged
merged 2 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

1. [16765](https://github.com/influxdata/influxdb/pull/16765): Extend influx cli pkg command with ability to take multiple files and directories
1. [16767](https://github.com/influxdata/influxdb/pull/16767): Extend influx cli pkg command with ability to take multiple urls, files, directories, and stdin at the same time
1. [16786](https://github.com/influxdata/influxdb/pull/16786): influx cli can manage secrets.

### Bug Fixes

Expand Down
9 changes: 4 additions & 5 deletions cmd/influx/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"time"

"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/cmd/influx/internal"
"github.com/influxdata/influxdb/http"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -108,7 +107,7 @@ func (b *cmdBucketBuilder) cmdCreateRunEFn(*cobra.Command, []string) error {
return fmt.Errorf("failed to create bucket: %v", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name", "Retention", "OrganizationID")
w.Write(map[string]interface{}{
"ID": bkt.ID.String(),
Expand Down Expand Up @@ -152,7 +151,7 @@ func (b *cmdBucketBuilder) cmdDeleteRunEFn(cmd *cobra.Command, args []string) er
return fmt.Errorf("failed to delete bucket with id %q: %v", id, err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name", "Retention", "OrganizationID", "Deleted")
w.Write(map[string]interface{}{
"ID": bkt.ID.String(),
Expand Down Expand Up @@ -225,7 +224,7 @@ func (b *cmdBucketBuilder) cmdFindRunEFn(cmd *cobra.Command, args []string) erro
return fmt.Errorf("failed to retrieve buckets: %s", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.HideHeaders(!b.headers)
w.WriteHeaders("ID", "Name", "Retention", "OrganizationID")
for _, b := range buckets {
Expand Down Expand Up @@ -291,7 +290,7 @@ func (b *cmdBucketBuilder) cmdUpdateRunEFn(cmd *cobra.Command, args []string) er
return fmt.Errorf("failed to update bucket: %v", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name", "Retention", "OrganizationID")
w.Write(map[string]interface{}{
"ID": bkt.ID.String(),
Expand Down
23 changes: 16 additions & 7 deletions cmd/influx/internal/tabwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,35 @@ import (
platform "github.com/influxdata/influxdb"
)

type tabWriter struct {
// TabWriter wraps tab writer headers logic.
type TabWriter struct {
writer *tabwriter.Writer
headers []string
hideHeaders bool
}

func NewTabWriter(w io.Writer) *tabWriter {
return &tabWriter{
// NewTabWriter creates a new tab writer.
func NewTabWriter(w io.Writer) *TabWriter {
return &TabWriter{
writer: tabwriter.NewWriter(w, 0, 8, 1, '\t', 0),
}
}

func (w *tabWriter) HideHeaders(b bool) {
// HideHeaders will set the hideHeaders flag.
func (w *TabWriter) HideHeaders(b bool) {
w.hideHeaders = b
}

func (w *tabWriter) WriteHeaders(h ...string) {
// WriteHeaders will write headers.
func (w *TabWriter) WriteHeaders(h ...string) {
w.headers = h
if !w.hideHeaders {
fmt.Fprintln(w.writer, strings.Join(h, "\t"))
}
}

func (w *tabWriter) Write(m map[string]interface{}) {
// Write will write the map into embed tab writer.
func (w *TabWriter) Write(m map[string]interface{}) {
body := make([]interface{}, len(w.headers))
types := make([]string, len(w.headers))
for i, h := range w.headers {
Expand All @@ -45,7 +50,11 @@ func (w *tabWriter) Write(m map[string]interface{}) {
fmt.Fprintf(w.writer, formatString+"\n", body...)
}

func (w *tabWriter) Flush() {
// Flush should be called after the last call to Write to ensure
// that any data buffered in the Writer is written to output. Any
// incomplete escape sequence at the end is considered
// complete for formatting purposes.
func (w *TabWriter) Flush() {
w.writer.Flush()
}

Expand Down
5 changes: 5 additions & 0 deletions cmd/influx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func (o genericCLIOpts) newCmd(use string, runE func(*cobra.Command, []string) e
return cmd
}

func (o genericCLIOpts) newTabWriter() *internal.TabWriter {
return internal.NewTabWriter(o.w)
}

func in(r io.Reader) genericCLIOptFn {
return func(o *genericCLIOpts) {
o.in = r
Expand Down Expand Up @@ -128,6 +132,7 @@ func influxCmd(opts ...genericCLIOptFn) *cobra.Command {
cmdQuery(),
cmdTranspile(),
cmdREPL(),
cmdSecret(runEWrapper),
cmdSetup(),
cmdTask(),
cmdUser(runEWrapper),
Expand Down
15 changes: 7 additions & 8 deletions cmd/influx/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/influxdata/influxdb/http"

"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/cmd/influx/internal"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -88,7 +87,7 @@ func (b *cmdOrgBuilder) createRunEFn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create organization: %v", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
w.Write(map[string]interface{}{
"ID": org.ID.String(),
Expand Down Expand Up @@ -138,7 +137,7 @@ func (b *cmdOrgBuilder) deleteRunEFn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to delete org with id %q: %v", id, err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name", "Deleted")
w.Write(map[string]interface{}{
"ID": o.ID.String(),
Expand Down Expand Up @@ -199,7 +198,7 @@ func (b *cmdOrgBuilder) findRunEFn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed find orgs: %v", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
for _, o := range orgs {
w.Write(map[string]interface{}{
Expand Down Expand Up @@ -269,7 +268,7 @@ func (b *cmdOrgBuilder) updateRunEFn(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to update org: %v", err)
}

w := internal.NewTabWriter(b.w)
w := b.newTabWriter()
w.WriteHeaders("ID", "Name")
w.Write(map[string]interface{}{
"ID": o.ID.String(),
Expand Down Expand Up @@ -348,7 +347,7 @@ func (b *cmdOrgBuilder) memberListRunEFn(cmd *cobra.Command, args []string) erro
}

ctx := context.Background()
return memberList(ctx, b.w, urmSVC, userSVC, influxdb.UserResourceMappingFilter{
return memberList(ctx, b, urmSVC, userSVC, influxdb.UserResourceMappingFilter{
ResourceType: influxdb.OrgsResourceType,
ResourceID: organization.ID,
UserType: influxdb.Member,
Expand Down Expand Up @@ -538,7 +537,7 @@ func newOrganizationService() (influxdb.OrganizationService, error) {
}, nil
}

func memberList(ctx context.Context, w io.Writer, urmSVC influxdb.UserResourceMappingService, userSVC influxdb.UserService, f influxdb.UserResourceMappingFilter) error {
func memberList(ctx context.Context, b *cmdOrgBuilder, urmSVC influxdb.UserResourceMappingService, userSVC influxdb.UserService, f influxdb.UserResourceMappingFilter) error {
mps, _, err := urmSVC.FindUserResourceMappings(ctx, f)
if err != nil {
return fmt.Errorf("failed to find members: %v", err)
Expand Down Expand Up @@ -582,7 +581,7 @@ func memberList(ctx context.Context, w io.Writer, urmSVC influxdb.UserResourceMa
}
}

tw := internal.NewTabWriter(w)
tw := b.newTabWriter()
tw.WriteHeaders("ID", "Name", "Status")
for _, m := range urs {
tw.Write(map[string]interface{}{
Expand Down
184 changes: 184 additions & 0 deletions cmd/influx/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package main

import (
"context"
"fmt"
"os"

"github.com/influxdata/influxdb"
"github.com/influxdata/influxdb/http"
"github.com/spf13/cobra"
input "github.com/tcnksm/go-input"
)

type secretSVCsFn func() (influxdb.SecretService, influxdb.OrganizationService, func(*input.UI) string, error)

func cmdSecret(opts ...genericCLIOptFn) *cobra.Command {
return newCmdSecretBuilder(newSecretSVCs, opts...).cmd()
}

type cmdSecretBuilder struct {
genericCLIOpts

svcFn secretSVCsFn

key string
org organization
}

func newCmdSecretBuilder(svcsFn secretSVCsFn, opts ...genericCLIOptFn) *cmdSecretBuilder {
opt := genericCLIOpts{
in: os.Stdin,
w: os.Stdout,
}
for _, o := range opts {
o(&opt)
}

return &cmdSecretBuilder{
genericCLIOpts: opt,
svcFn: svcsFn,
}
}

func (b *cmdSecretBuilder) cmd() *cobra.Command {
cmd := b.newCmd("secret", nil)
cmd.Short = "Secret management commands"
cmd.Run = seeHelp
cmd.AddCommand(
b.cmdDelete(),
b.cmdFind(),
b.cmdUpdate(),
)
return cmd
}

func (b *cmdSecretBuilder) cmdUpdate() *cobra.Command {
cmd := b.newCmd("update", b.cmdUpdateRunEFn)
cmd.Short = "Update secret"
cmd.Flags().StringVarP(&b.key, "key", "k", "", "The secret key (required)")
cmd.MarkFlagRequired("key")
b.org.register(cmd, false)
Comment on lines +57 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass a key, but how do you update the value of the secret?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sanderson This is done through a prompt similar to our password flow. I'm adding a card to provide it over the CLI as an arg in the same command.


return cmd
}

func (b *cmdSecretBuilder) cmdDelete() *cobra.Command {
cmd := b.newCmd("delete", b.cmdDeleteRunEFn)
cmd.Short = "Delete secret"

cmd.Flags().StringVarP(&b.key, "key", "k", "", "The secret key (required)")
cmd.MarkFlagRequired("key")
b.org.register(cmd, false)

return cmd
}

func (b *cmdSecretBuilder) cmdUpdateRunEFn(cmd *cobra.Command, args []string) error {
scrSVC, orgSVC, getSecretFn, err := b.svcFn()
if err != nil {
return err
}
orgID, err := b.org.getID(orgSVC)
if err != nil {
return err
}

ctx := context.Background()

ui := &input.UI{
Writer: b.genericCLIOpts.w,
Reader: b.genericCLIOpts.in,
}
secret := getSecretFn(ui)

if err := scrSVC.PatchSecrets(ctx, orgID, map[string]string{b.key: secret}); err != nil {
return fmt.Errorf("failed to update secret with key %q: %v", b.key, err)
}

w := b.newTabWriter()
w.WriteHeaders("Key", "OrgID", "Updated")
w.Write(map[string]interface{}{
"Key": b.key,
"OrgID": orgID,
"Updated": true,
})
w.Flush()

return nil
}

func (b *cmdSecretBuilder) cmdDeleteRunEFn(cmd *cobra.Command, args []string) error {
scrSVC, orgSVC, _, err := b.svcFn()
if err != nil {
return err
}
orgID, err := b.org.getID(orgSVC)
if err != nil {
return err
}

ctx := context.Background()
if err := scrSVC.DeleteSecret(ctx, orgID, b.key); err != nil {
return fmt.Errorf("failed to delete secret with key %q: %v", b.key, err)
}

w := b.newTabWriter()
w.WriteHeaders("Key", "OrgID", "Deleted")
w.Write(map[string]interface{}{
"Key": b.key,
"OrgID": orgID,
"Deleted": true,
})
w.Flush()

return nil
}

func (b *cmdSecretBuilder) cmdFind() *cobra.Command {
cmd := b.newCmd("find", b.cmdFindRunEFn)
cmd.Short = "Find secrets"
b.org.register(cmd, false)
Comment on lines +139 to +141
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an overall critique of "find"/"list" commands throughout the influx CLI. We are inconsistent. For some it's "list" and others it's "find". The term "find" indicates a searching action and I would expect ways to filter the results. This just lists all the secret keys.

My personal preference would be to make everything consistent and use either list or ls to align with standard Linux/UNIX command line tools.

@jademcgough

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sanderson, this x1000! I've had the same feelings towards list|find. I would really love to see it become list if it does not allow for filtering. We do have filtering on some Find commands, either by name or what not. But these are still akin to list operations for me in most nix envs. I would love to see list and ls as an alias shorthand for calling it 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sanderson we're going to make these changes 👍. @influxdata/tools-team is on board 🤘

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsteenb2 🎉


return cmd
}

func (b *cmdSecretBuilder) cmdFindRunEFn(cmd *cobra.Command, args []string) error {

scrSVC, orgSVC, _, err := b.svcFn()
if err != nil {
return err
}

orgID, err := b.org.getID(orgSVC)
if err != nil {
return err
}

secrets, err := scrSVC.GetSecretKeys(context.Background(), orgID)
if err != nil {
return fmt.Errorf("failed to retrieve secret keys: %s", err)
}

w := b.newTabWriter()
w.WriteHeaders("Key", "OrganizationID")
for _, s := range secrets {
w.Write(map[string]interface{}{
"Key": s,
"OrganizationID": orgID,
})
}
w.Flush()

return nil
}

func newSecretSVCs() (influxdb.SecretService, influxdb.OrganizationService, func(*input.UI) string, error) {
httpClient, err := newHTTPClient()
if err != nil {
return nil, nil, nil, err
}
orgSvc := &http.OrganizationService{Client: httpClient}

return &http.SecretService{Client: httpClient}, orgSvc, getSecret, nil
}
Loading