mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
ci: enable nestif linter (#9363)
This commit is contained in:
+3
-1
@@ -131,7 +131,8 @@ linters-settings:
|
|||||||
- trialer
|
- trialer
|
||||||
|
|
||||||
nestif:
|
nestif:
|
||||||
min-complexity: 4 # Min complexity of if statements (def 5, goal 4)
|
# goal: 10
|
||||||
|
min-complexity: 20
|
||||||
|
|
||||||
revive:
|
revive:
|
||||||
# see https://github.com/mgechev/revive#available-rules for details.
|
# see https://github.com/mgechev/revive#available-rules for details.
|
||||||
@@ -237,6 +238,7 @@ linters:
|
|||||||
# create a good culture around cognitive complexity.
|
# create a good culture around cognitive complexity.
|
||||||
# - gocyclo
|
# - gocyclo
|
||||||
- gocognit
|
- gocognit
|
||||||
|
- nestif
|
||||||
- goimports
|
- goimports
|
||||||
- gomodguard
|
- gomodguard
|
||||||
- gosec
|
- gosec
|
||||||
|
|||||||
+105
-60
@@ -37,6 +37,95 @@ func init() {
|
|||||||
browser.Stdout = io.Discard
|
browser.Stdout = io.Discard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptFirstUsername(inv *clibase.Invocation) (string, error) {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Errorf("get current user: %w", err)
|
||||||
|
}
|
||||||
|
username, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||||
|
Text: "What " + cliui.DefaultStyles.Field.Render("username") + " would you like?",
|
||||||
|
Default: currentUser.Username,
|
||||||
|
})
|
||||||
|
if errors.Is(err, cliui.Canceled) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func promptFirstPassword(inv *clibase.Invocation) (string, error) {
|
||||||
|
retry:
|
||||||
|
password, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||||
|
Text: "Enter a " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||||
|
Secret: true,
|
||||||
|
Validate: func(s string) error {
|
||||||
|
return userpassword.Validate(s)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Errorf("specify password prompt: %w", err)
|
||||||
|
}
|
||||||
|
confirm, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||||
|
Text: "Confirm " + cliui.DefaultStyles.Field.Render("password") + ":",
|
||||||
|
Secret: true,
|
||||||
|
Validate: cliui.ValidateNotEmpty,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", xerrors.Errorf("confirm password prompt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm != password {
|
||||||
|
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Error.Render("Passwords do not match"))
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
|
||||||
|
return password, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootCmd) loginWithPassword(
|
||||||
|
inv *clibase.Invocation,
|
||||||
|
client *codersdk.Client,
|
||||||
|
email, password string,
|
||||||
|
) error {
|
||||||
|
resp, err := client.LoginWithPassword(inv.Context(), codersdk.LoginWithPasswordRequest{
|
||||||
|
Email: email,
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("login with password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionToken := resp.SessionToken
|
||||||
|
config := r.createConfig()
|
||||||
|
err = config.Session().Write(sessionToken)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("write session token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetSessionToken(sessionToken)
|
||||||
|
|
||||||
|
// Nice side-effect: validates the token.
|
||||||
|
u, err := client.User(inv.Context(), "me")
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(
|
||||||
|
inv.Stdout,
|
||||||
|
cliui.DefaultStyles.Paragraph.Render(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Welcome to Coder, %s! You're authenticated.",
|
||||||
|
cliui.DefaultStyles.Keyword.Render(u.Username),
|
||||||
|
),
|
||||||
|
)+"\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RootCmd) login() *clibase.Cmd {
|
func (r *RootCmd) login() *clibase.Cmd {
|
||||||
const firstUserTrialEnv = "CODER_FIRST_USER_TRIAL"
|
const firstUserTrialEnv = "CODER_FIRST_USER_TRIAL"
|
||||||
|
|
||||||
@@ -91,41 +180,30 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||||||
_, _ = fmt.Fprintln(inv.Stderr, cliui.DefaultStyles.Warn.Render(err.Error()))
|
_, _ = fmt.Fprintln(inv.Stderr, cliui.DefaultStyles.Warn.Render(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
hasInitialUser, err := client.HasFirstUser(ctx)
|
hasFirstUser, err := client.HasFirstUser(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
|
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
|
||||||
}
|
}
|
||||||
if !hasInitialUser {
|
if !hasFirstUser {
|
||||||
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Your Coder deployment hasn't been set up!\n")
|
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Your Coder deployment hasn't been set up!\n")
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
if !isTTY(inv) {
|
if !isTTY(inv) {
|
||||||
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
|
return xerrors.New("the initial user cannot be created in non-interactive mode. use the API")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := cliui.Prompt(inv, cliui.PromptOptions{
|
_, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||||
Text: "Would you like to create the first user?",
|
Text: "Would you like to create the first user?",
|
||||||
Default: cliui.ConfirmYes,
|
Default: cliui.ConfirmYes,
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
})
|
})
|
||||||
if errors.Is(err, cliui.Canceled) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
currentUser, err := user.Current()
|
|
||||||
|
username, err = promptFirstUsername(inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get current user: %w", err)
|
return err
|
||||||
}
|
|
||||||
username, err = cliui.Prompt(inv, cliui.PromptOptions{
|
|
||||||
Text: "What " + cliui.DefaultStyles.Field.Render("username") + " would you like?",
|
|
||||||
Default: currentUser.Username,
|
|
||||||
})
|
|
||||||
if errors.Is(err, cliui.Canceled) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("pick username prompt: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,37 +219,14 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("specify email prompt: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if password == "" {
|
if password == "" {
|
||||||
var matching bool
|
password, err = promptFirstPassword(inv)
|
||||||
|
if err != nil {
|
||||||
for !matching {
|
return err
|
||||||
password, err = cliui.Prompt(inv, cliui.PromptOptions{
|
|
||||||
Text: "Enter a " + cliui.DefaultStyles.Field.Render("password") + ":",
|
|
||||||
Secret: true,
|
|
||||||
Validate: func(s string) error {
|
|
||||||
return userpassword.Validate(s)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("specify password prompt: %w", err)
|
|
||||||
}
|
|
||||||
confirm, err := cliui.Prompt(inv, cliui.PromptOptions{
|
|
||||||
Text: "Confirm " + cliui.DefaultStyles.Field.Render("password") + ":",
|
|
||||||
Secret: true,
|
|
||||||
Validate: cliui.ValidateNotEmpty,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("confirm password prompt: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
matching = confirm == password
|
|
||||||
if !matching {
|
|
||||||
_, _ = fmt.Fprintln(inv.Stdout, cliui.DefaultStyles.Error.Render("Passwords do not match"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,29 +248,19 @@ func (r *RootCmd) login() *clibase.Cmd {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("create initial user: %w", err)
|
return xerrors.Errorf("create initial user: %w", err)
|
||||||
}
|
}
|
||||||
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
|
|
||||||
Email: email,
|
err := r.loginWithPassword(inv, client, email, password)
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("login with password: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionToken := resp.SessionToken
|
err = r.createConfig().URL().Write(serverURL.String())
|
||||||
config := r.createConfig()
|
|
||||||
err = config.Session().Write(sessionToken)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("write session token: %w", err)
|
|
||||||
}
|
|
||||||
err = config.URL().Write(serverURL.String())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("write server url: %w", err)
|
return xerrors.Errorf("write server url: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(inv.Stdout,
|
_, _ = fmt.Fprintf(
|
||||||
cliui.DefaultStyles.Paragraph.Render(fmt.Sprintf("Welcome to Coder, %s! You're authenticated.", cliui.DefaultStyles.Keyword.Render(username)))+"\n")
|
inv.Stdout,
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(inv.Stdout,
|
|
||||||
cliui.DefaultStyles.Paragraph.Render("Get started by creating a template: "+cliui.DefaultStyles.Code.Render("coder templates init"))+"\n")
|
cliui.DefaultStyles.Paragraph.Render("Get started by creating a template: "+cliui.DefaultStyles.Code.Render("coder templates init"))+"\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -97,16 +97,15 @@ func TestLogin(t *testing.T) {
|
|||||||
t.Run("InitialUserFlags", func(t *testing.T) {
|
t.Run("InitialUserFlags", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, nil)
|
client := coderdtest.New(t, nil)
|
||||||
doneChan := make(chan struct{})
|
inv, _ := clitest.New(
|
||||||
root, _ := clitest.New(t, "login", client.URL.String(), "--first-user-username", "testuser", "--first-user-email", "user@coder.com", "--first-user-password", "SomeSecurePassword!", "--first-user-trial")
|
t, "login", client.URL.String(),
|
||||||
pty := ptytest.New(t).Attach(root)
|
"--first-user-username", "testuser", "--first-user-email", "user@coder.com",
|
||||||
go func() {
|
"--first-user-password", "SomeSecurePassword!", "--first-user-trial",
|
||||||
defer close(doneChan)
|
)
|
||||||
err := root.Run()
|
pty := ptytest.New(t).Attach(inv)
|
||||||
assert.NoError(t, err)
|
w := clitest.StartWithWaiter(t, inv)
|
||||||
}()
|
|
||||||
pty.ExpectMatch("Welcome to Coder")
|
pty.ExpectMatch("Welcome to Coder")
|
||||||
<-doneChan
|
w.RequireSuccess()
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
|
t.Run("InitialUserTTYConfirmPasswordFailAndReprompt", func(t *testing.T) {
|
||||||
|
|||||||
+83
-80
@@ -279,10 +279,15 @@ func (h *Handler) serveHTML(resp http.ResponseWriter, request *http.Request, req
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execTmpl(tmpl *template.Template, state htmlState) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := tmpl.Execute(&buf, state)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
// renderWithState will render the file using the given nonce if the file exists
|
// renderWithState will render the file using the given nonce if the file exists
|
||||||
// as a template. If it does not, it will return an error.
|
// as a template. If it does not, it will return an error.
|
||||||
func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state htmlState) ([]byte, error) {
|
func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state htmlState) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
filePath = "index.html"
|
filePath = "index.html"
|
||||||
}
|
}
|
||||||
@@ -307,96 +312,94 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
|||||||
RedirectToLogin: false,
|
RedirectToLogin: false,
|
||||||
SessionTokenFunc: nil,
|
SessionTokenFunc: nil,
|
||||||
})
|
})
|
||||||
if ok && apiKey != nil && actor != nil {
|
if !ok || apiKey == nil || actor == nil {
|
||||||
ctx := dbauthz.As(r.Context(), actor.Actor)
|
return execTmpl(tmpl, state)
|
||||||
|
}
|
||||||
|
|
||||||
var eg errgroup.Group
|
ctx := dbauthz.As(r.Context(), actor.Actor)
|
||||||
var user database.User
|
|
||||||
orgIDs := []uuid.UUID{}
|
var eg errgroup.Group
|
||||||
eg.Go(func() error {
|
var user database.User
|
||||||
var err error
|
orgIDs := []uuid.UUID{}
|
||||||
user, err = h.opts.Database.GetUserByID(ctx, apiKey.UserID)
|
eg.Go(func() error {
|
||||||
return err
|
var err error
|
||||||
})
|
user, err = h.opts.Database.GetUserByID(ctx, apiKey.UserID)
|
||||||
eg.Go(func() error {
|
return err
|
||||||
memberIDs, err := h.opts.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{apiKey.UserID})
|
})
|
||||||
if errors.Is(err, sql.ErrNoRows) || len(memberIDs) == 0 {
|
eg.Go(func() error {
|
||||||
return nil
|
memberIDs, err := h.opts.Database.GetOrganizationIDsByMemberIDs(ctx, []uuid.UUID{apiKey.UserID})
|
||||||
|
if errors.Is(err, sql.ErrNoRows) || len(memberIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
orgIDs = memberIDs[0].OrganizationIDs
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
err := eg.Wait()
|
||||||
|
if err == nil {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
user, err := json.Marshal(db2sdk.User(user, orgIDs))
|
||||||
|
if err == nil {
|
||||||
|
state.User = html.EscapeString(string(user))
|
||||||
}
|
}
|
||||||
if err != nil {
|
}()
|
||||||
return nil
|
entitlements := h.Entitlements.Load()
|
||||||
}
|
if entitlements != nil {
|
||||||
orgIDs = memberIDs[0].OrganizationIDs
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
err := eg.Wait()
|
|
||||||
if err == nil {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
user, err := json.Marshal(db2sdk.User(user, orgIDs))
|
entitlements, err := json.Marshal(entitlements)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
state.User = html.EscapeString(string(user))
|
state.Entitlements = html.EscapeString(string(entitlements))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
entitlements := h.Entitlements.Load()
|
|
||||||
if entitlements != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
entitlements, err := json.Marshal(entitlements)
|
|
||||||
if err == nil {
|
|
||||||
state.Entitlements = html.EscapeString(string(entitlements))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if h.AppearanceFetcher != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
cfg, err := h.AppearanceFetcher(ctx)
|
|
||||||
if err == nil {
|
|
||||||
appearance, err := json.Marshal(cfg)
|
|
||||||
if err == nil {
|
|
||||||
state.Appearance = html.EscapeString(string(appearance))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if h.RegionsFetcher != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
regions, err := h.RegionsFetcher(ctx)
|
|
||||||
if err == nil {
|
|
||||||
regions, err := json.Marshal(regions)
|
|
||||||
if err == nil {
|
|
||||||
state.Regions = html.EscapeString(string(regions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
experiments := h.Experiments.Load()
|
|
||||||
if experiments != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
experiments, err := json.Marshal(experiments)
|
|
||||||
if err == nil {
|
|
||||||
state.Experiments = html.EscapeString(string(experiments))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
if h.AppearanceFetcher != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
cfg, err := h.AppearanceFetcher(ctx)
|
||||||
|
if err == nil {
|
||||||
|
appearance, err := json.Marshal(cfg)
|
||||||
|
if err == nil {
|
||||||
|
state.Appearance = html.EscapeString(string(appearance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if h.RegionsFetcher != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
regions, err := h.RegionsFetcher(ctx)
|
||||||
|
if err == nil {
|
||||||
|
regions, err := json.Marshal(regions)
|
||||||
|
if err == nil {
|
||||||
|
state.Regions = html.EscapeString(string(regions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
experiments := h.Experiments.Load()
|
||||||
|
if experiments != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
experiments, err := json.Marshal(experiments)
|
||||||
|
if err == nil {
|
||||||
|
state.Experiments = html.EscapeString(string(experiments))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tmpl.Execute(&buf, state)
|
return execTmpl(tmpl, state)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// noopResponseWriter is a response writer that does nothing.
|
// noopResponseWriter is a response writer that does nothing.
|
||||||
|
|||||||
Reference in New Issue
Block a user