diff --git a/cmd/chronoctl/add.go b/cmd/chronoctl/add.go new file mode 100644 index 0000000000..0a26c72c98 --- /dev/null +++ b/cmd/chronoctl/add.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + + "github.com/influxdata/chronograf" +) + +type AddCommand struct { + BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` + ID *uint64 `short:"i" long:"id" description:"Users ID. Must be id for existing user"` + Username string `short:"n" long:"name" description:"Users name. Must be Oauth-able email address or username"` + Provider string `short:"p" long:"provider" description:"Name of the Auth provider (e.g. google, github, auth0, or generic)"` + Scheme string `short:"s" long:"scheme" description:"Authentication scheme that matches auth provider (e.g. oauth or ldap)"` + //Organizations string `short:"o" long:"orgs" description:"A comma separated list of organizations that the user should be added to"` +} + +var addCommand AddCommand + +func (l *AddCommand) Execute(args []string) error { + c, err := NewBoltClient(l.BoltPath) + if err != nil { + return err + } + defer c.Close() + + q := chronograf.UserQuery{ + Name: &l.Username, + Provider: &l.Provider, + Scheme: &l.Scheme, + } + + if l.ID != nil { + q.ID = l.ID + } + + ctx := context.Background() + + user, err := c.UsersStore.Get(ctx, q) + if err != nil && err != chronograf.ErrUserNotFound { + return err + } else if err == chronograf.ErrUserNotFound { + user = &chronograf.User{ + Name: l.Username, + Provider: l.Provider, + Scheme: l.Scheme, + SuperAdmin: true, + } + + user, err = c.UsersStore.Add(ctx, user) + if err != nil { + return err + } + } else { + user.SuperAdmin = true + if err = c.UsersStore.Update(ctx, user); err != nil { + return err + } + } + + // TODO(desa): Apply mapping to user and update their roles + // TODO(desa): Add a flag that allows the user to specify an organization to join + + w := NewTabWriter() + WriteHeaders(w) + WriteUser(w, user) + w.Flush() + + return nil +} + +func init() { + parser.AddCommand("add-superadmin", + "Creates a new superadmin user", + "The add-user command will create a new user with superadmin status", + &addCommand) +} diff --git a/cmd/chronoctl/list.go b/cmd/chronoctl/list.go new file mode 100644 index 0000000000..6396359adf --- /dev/null +++ b/cmd/chronoctl/list.go @@ -0,0 +1,41 @@ +package main + +import ( + "context" +) + +type ListCommand struct { + BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` +} + +var listCommand ListCommand + +func (l *ListCommand) Execute(args []string) error { + c, err := NewBoltClient(l.BoltPath) + if err != nil { + return err + } + defer c.Close() + + ctx := context.Background() + users, err := c.UsersStore.All(ctx) + if err != nil { + return err + } + + w := NewTabWriter() + WriteHeaders(w) + for _, user := range users { + WriteUser(w, &user) + } + w.Flush() + + return nil +} + +func init() { + parser.AddCommand("list-users", + "Lists users", + "The list-users command will list all users in the chronograf boltdb instance", + &listCommand) +} diff --git a/cmd/chronoctl/main.go b/cmd/chronoctl/main.go new file mode 100644 index 0000000000..b3260fb217 --- /dev/null +++ b/cmd/chronoctl/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "os" + + "github.com/jessevdk/go-flags" +) + +type Options struct { +} + +var options Options + +var parser = flags.NewParser(&options, flags.Default) + +func main() { + if _, err := parser.Parse(); err != nil { + if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp { + os.Exit(0) + } else { + os.Exit(1) + } + } + +} diff --git a/cmd/chronoctl/util.go b/cmd/chronoctl/util.go new file mode 100644 index 0000000000..f40635dbd9 --- /dev/null +++ b/cmd/chronoctl/util.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/bolt" + "github.com/influxdata/chronograf/mocks" +) + +func NewBoltClient(path string) (*bolt.Client, error) { + c := bolt.NewClient() + c.Path = path + + ctx := context.Background() + logger := mocks.NewLogger() + var bi chronograf.BuildInfo + if err := c.Open(ctx, logger, bi); err != nil { + return nil, err + } + + return c, nil +} + +func NewTabWriter() *tabwriter.Writer { + return tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) +} + +func WriteHeaders(w io.Writer) { + fmt.Fprintln(w, "ID\tName\tProvider\tScheme\tSuperAdmin\tOrganization(s)") +} + +func WriteUser(w io.Writer, user *chronograf.User) { + orgs := []string{} + for _, role := range user.Roles { + orgs = append(orgs, role.Organization) + } + fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%t\t%s\n", user.ID, user.Name, user.Provider, user.Scheme, user.SuperAdmin, strings.Join(orgs, ",")) +}