Skip to content

Commit

Permalink
Admin: add src admin create command (#957)
Browse files Browse the repository at this point in the history
* Add `src admin create` command

* Add command to setup an initial admin account on a new deployment of Sourcegraph.

* Add option to use env var for admin pass

* Fixing issues pointed out in PR
  • Loading branch information
jdpleiness committed Mar 16, 2023
1 parent 874985b commit 097cf0e
Show file tree
Hide file tree
Showing 4 changed files with 596 additions and 0 deletions.
38 changes: 38 additions & 0 deletions cmd/src/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"flag"
"fmt"
)

var adminCommands commander

func init() {
usage := `'src admin' is a tool that manages an initial admin user on a new Sourcegraph instance.
Usage:
src admin create [command options]
The commands are:
create create an initial admin user
Use "src admin [command] -h" for more information about a command.
`

flagSet := flag.NewFlagSet("admin", flag.ExitOnError)
handler := func(args []string) error {
adminCommands.run(flagSet, "srv admin", usage, args)
return nil
}

commands = append(commands, &command{
flagSet: flagSet,
aliases: []string{"admin"},
handler: handler,
usageFunc: func() {
fmt.Println(usage)
},
})
}
100 changes: 100 additions & 0 deletions cmd/src/admin_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/sourcegraph/src-cli/internal/users"

"github.com/sourcegraph/sourcegraph/lib/errors"
)

func init() {
usage := `
Examples:
Create an initial admin user on a new Sourcegraph deployment:
$ src admin create -url https://your-sourcegraph-url -username admin -email admin@yourcompany.com -with-token
Create an initial admin user on a new Sourcegraph deployment using '-password' flag.
WARNING: for security purposes we strongly recommend using the SRC_ADMIN_PASS environment variable when possible.
$ src admin create -url https://your-sourcegraph-url -username admin -email admin@yourcompany.com -password p@55w0rd -with-token
Environmental variables
SRC_ADMIN_PASS The new admin user's password
`

flagSet := flag.NewFlagSet("create", flag.ExitOnError)
usageFunc := func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src users %s':\n", flagSet.Name())
flagSet.PrintDefaults()
fmt.Println(usage)
}

var (
urlFlag = flagSet.String("url", "", "The base URL for the Sourcegraph instance.")
usernameFlag = flagSet.String("username", "", "The new admin user's username.")
emailFlag = flagSet.String("email", "", "The new admin user's email address.")
passwordFlag = flagSet.String("password", "", "The new admin user's password.")
tokenFlag = flagSet.Bool("with-token", false, "Optionally create and output an admin access token.")
)

handler := func(args []string) error {
if err := flagSet.Parse(args); err != nil {
return err
}

ok, _, err := users.NeedsSiteInit(*urlFlag)
if err != nil {
return err
}
if !ok {
return errors.New("failed to create admin, site already initialized")
}

envAdminPass := os.Getenv("SRC_ADMIN_PASS")

var client *users.Client

switch {
case envAdminPass != "" && *passwordFlag == "":
client, err = users.SiteAdminInit(*urlFlag, *emailFlag, *usernameFlag, envAdminPass)
if err != nil {
return err
}
case envAdminPass == "" && *passwordFlag != "":
client, err = users.SiteAdminInit(*urlFlag, *emailFlag, *usernameFlag, *passwordFlag)
if err != nil {
return err
}
case envAdminPass != "" && *passwordFlag != "":
return errors.New("failed to read admin password: environment variable and -password flag both set")
case envAdminPass == "" && *passwordFlag == "":
return errors.New("failed to read admin password from 'SRC_ADMIN_PASS' environment variable or -password flag")
}

if *tokenFlag {
token, err := client.CreateAccessToken("", []string{"user:all", "site-admin:sudo"}, "src-cli")
if err != nil {
return err
}

_, err = fmt.Fprintf(flag.CommandLine.Output(), "%s\n", token)
if err != nil {
return err
}
}

return nil
}

adminCommands = append(adminCommands, &command{
flagSet: flagSet,
handler: handler,
usageFunc: usageFunc,
})
}
128 changes: 128 additions & 0 deletions internal/lazyregexp/lazyregexp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package lazyregexp is a thin wrapper over regexp, allowing the use of global
// regexp variables without forcing them to be compiled at init.
package lazyregexp

import (
"os"
"strings"
"sync"

"github.com/grafana/regexp"
)

// Regexp is a wrapper around regexp.Regexp, where the underlying regexp will be
// compiled the first time it is needed.
type Regexp struct {
str string
posix bool
once sync.Once
rx *regexp.Regexp
}

func (r *Regexp) Re() *regexp.Regexp {
r.once.Do(r.build)
return r.rx
}

func (r *Regexp) build() {
if r.posix {
r.rx = regexp.MustCompilePOSIX(r.str)
} else {
r.rx = regexp.MustCompile(r.str)
}
r.str = ""
}

func (r *Regexp) FindSubmatch(s []byte) [][]byte {
return r.Re().FindSubmatch(s)
}

func (r *Regexp) FindStringSubmatch(s string) []string {
return r.Re().FindStringSubmatch(s)
}

func (r *Regexp) FindStringSubmatchIndex(s string) []int {
return r.Re().FindStringSubmatchIndex(s)
}

func (r *Regexp) ReplaceAllString(src, repl string) string {
return r.Re().ReplaceAllString(src, repl)
}

func (r *Regexp) FindString(s string) string {
return r.Re().FindString(s)
}

func (r *Regexp) FindAllString(s string, n int) []string {
return r.Re().FindAllString(s, n)
}

func (r *Regexp) MatchString(s string) bool {
return r.Re().MatchString(s)
}

func (r *Regexp) SubexpNames() []string {
return r.Re().SubexpNames()
}

func (r *Regexp) FindAllStringSubmatch(s string, n int) [][]string {
return r.Re().FindAllStringSubmatch(s, n)
}

func (r *Regexp) Split(s string, n int) []string {
return r.Re().Split(s, n)
}

func (r *Regexp) ReplaceAllLiteralString(src, repl string) string {
return r.Re().ReplaceAllLiteralString(src, repl)
}

func (r *Regexp) FindAllIndex(b []byte, n int) [][]int {
return r.Re().FindAllIndex(b, n)
}

func (r *Regexp) Match(b []byte) bool {
return r.Re().Match(b)
}

func (r *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string {
return r.Re().ReplaceAllStringFunc(src, repl)
}

func (r *Regexp) ReplaceAll(src, repl []byte) []byte {
return r.Re().ReplaceAll(src, repl)
}

func (r *Regexp) SubexpIndex(s string) int {
return r.Re().SubexpIndex(s)
}

var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")

// New creates a new lazy regexp, delaying the compiling work until it is first
// needed. If the code is being run as part of tests, the regexp compiling will
// happen immediately.
func New(str string) *Regexp {
lr := &Regexp{str: str}
if inTest {
// In tests, always compile the regexps early.
lr.Re()
}
return lr
}

// NewPOSIX creates a new lazy regexp, delaying the compiling work until it is
// first needed. If the code is being run as part of tests, the regexp
// compiling will happen immediately.
func NewPOSIX(str string) *Regexp {
lr := &Regexp{str: str, posix: true}
if inTest {
// In tests, always compile the regexps early.
lr.Re()
}
return lr
}
Loading

0 comments on commit 097cf0e

Please sign in to comment.