Skip to content

Commit 10ccce1

Browse files
committed
feat: expose 30d trial form via CLI
1 parent 79d24d2 commit 10ccce1

File tree

5 files changed

+359
-1
lines changed

5 files changed

+359
-1
lines changed

cli/login.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ func (r *RootCmd) login() *serpent.Command {
149149
password string
150150
trial bool
151151
useTokenForSession bool
152+
153+
firstName string
154+
lastName string
155+
phoneNumber string
156+
jobTitle string
157+
companyName string
158+
country string
159+
developers string
152160
)
153161
cmd := &serpent.Command{
154162
Use: "login [<url>]",
@@ -267,12 +275,66 @@ func (r *RootCmd) login() *serpent.Command {
267275
trial = v == "yes" || v == "y"
268276
}
269277

278+
if trial {
279+
if firstName == "" {
280+
firstName, err = promptTrialInfo(inv, "firstName")
281+
if err != nil {
282+
return err
283+
}
284+
}
285+
if lastName == "" {
286+
lastName, err = promptTrialInfo(inv, "lastName")
287+
if err != nil {
288+
return err
289+
}
290+
}
291+
if phoneNumber == "" {
292+
phoneNumber, err = promptTrialInfo(inv, "phoneNumber")
293+
if err != nil {
294+
return err
295+
}
296+
}
297+
if jobTitle == "" {
298+
jobTitle, err = promptTrialInfo(inv, "jobTitle")
299+
if err != nil {
300+
return err
301+
}
302+
}
303+
if companyName == "" {
304+
companyName, err = promptTrialInfo(inv, "companyName")
305+
if err != nil {
306+
return err
307+
}
308+
}
309+
if country == "" {
310+
country, err = promptCountry(inv)
311+
if err != nil {
312+
return err
313+
}
314+
}
315+
if developers == "" {
316+
developers, err = promptDevelopers(inv)
317+
if err != nil {
318+
return err
319+
}
320+
}
321+
}
322+
270323
_, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{
271324
Email: email,
272325
Username: username,
273326
Name: name,
274327
Password: password,
275328
Trial: trial,
329+
TrialInfo: codersdk.CreateFirstUserTrialInfo{
330+
FirstName: firstName,
331+
LastName: lastName,
332+
PhoneNumber: phoneNumber,
333+
JobTitle: jobTitle,
334+
CompanyName: companyName,
335+
Country: country,
336+
Developers: developers,
337+
},
276338
})
277339
if err != nil {
278340
return xerrors.Errorf("create initial user: %w", err)
@@ -398,6 +460,48 @@ func (r *RootCmd) login() *serpent.Command {
398460
Description: "By default, the CLI will generate a new session token when logging in. This flag will instead use the provided token as the session token.",
399461
Value: serpent.BoolOf(&useTokenForSession),
400462
},
463+
{
464+
Flag: "first-user-first-name",
465+
Env: "CODER_FIRST_USER_FIRST_NAME",
466+
Description: "Specifies the first name of the user.",
467+
Value: serpent.StringOf(&firstName),
468+
},
469+
{
470+
Flag: "first-user-last-name",
471+
Env: "CODER_FIRST_USER_LAST_NAME",
472+
Description: "Specifies the last name of the user.",
473+
Value: serpent.StringOf(&lastName),
474+
},
475+
{
476+
Flag: "first-user-phone-number",
477+
Env: "CODER_FIRST_USER_PHONE_NUMBER",
478+
Description: "Specifies the phone number of the user.",
479+
Value: serpent.StringOf(&phoneNumber),
480+
},
481+
{
482+
Flag: "first-user-job-title",
483+
Env: "CODER_FIRST_USER_JOB_TITLE",
484+
Description: "Specifies the job title of the user.",
485+
Value: serpent.StringOf(&jobTitle),
486+
},
487+
{
488+
Flag: "first-user-company-name",
489+
Env: "CODER_FIRST_USER_COMPANY_NAME",
490+
Description: "Specifies the company name of the user.",
491+
Value: serpent.StringOf(&companyName),
492+
},
493+
{
494+
Flag: "first-user-country",
495+
Env: "CODER_FIRST_USER_COUNTRY",
496+
Description: "Specifies the country of the user.",
497+
Value: serpent.StringOf(&country),
498+
},
499+
{
500+
Flag: "first-user-developers",
501+
Env: "CODER_FIRST_USER_DEVELOPERS",
502+
Description: "Specifies the number of developers.",
503+
Value: serpent.StringOf(&developers),
504+
},
401505
}
402506
return cmd
403507
}
@@ -449,3 +553,75 @@ func openURL(inv *serpent.Invocation, urlToOpen string) error {
449553

450554
return browser.OpenURL(urlToOpen)
451555
}
556+
557+
func promptTrialInfo(inv *serpent.Invocation, fieldName string) (string, error) {
558+
value, err := cliui.Prompt(inv, cliui.PromptOptions{
559+
Text: fmt.Sprintf("Please enter %s:", pretty.Sprint(cliui.DefaultStyles.Field, fieldName)),
560+
Validate: func(s string) error {
561+
if strings.TrimSpace(s) == "" {
562+
return xerrors.Errorf("%s is required", fieldName)
563+
}
564+
return nil
565+
},
566+
})
567+
if err != nil {
568+
if errors.Is(err, cliui.Canceled) {
569+
return "", nil
570+
}
571+
return "", err
572+
}
573+
return value, nil
574+
}
575+
576+
func promptDevelopers(inv *serpent.Invocation) (string, error) {
577+
options := []string{"1-100", "101-500", "501-1000", "1001-2500", "2500+"}
578+
selection, err := cliui.Select(inv, cliui.SelectOptions{
579+
Options: options,
580+
HideSearch: false,
581+
Message: "Select the number of developers:",
582+
})
583+
if err != nil {
584+
return "", xerrors.Errorf("select developers: %w", err)
585+
}
586+
return selection, nil
587+
}
588+
589+
func promptCountry(inv *serpent.Invocation) (string, error) {
590+
countries := []string{
591+
"Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda",
592+
"Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados",
593+
"Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia, Plurinational State of", "Bonaire, Sint Eustatius and Saba", "Bosnia and Herzegovina", "Botswana",
594+
"Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada",
595+
"Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros",
596+
"Congo", "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Côte d'Ivoire", "Croatia", "Cuba", "Curaçao", "Cyprus", "Czech Republic",
597+
"Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia",
598+
"Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern Territories", "Gabon",
599+
"Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam",
600+
"Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See (Vatican City State)", "Honduras", "Hong Kong",
601+
"Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy",
602+
"Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait",
603+
"Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
604+
"Macao", "Macedonia, the Former Yugoslav Republic of", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique",
605+
"Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia", "Montenegro", "Montserrat",
606+
"Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Caledonia", "New Zealand", "Nicaragua",
607+
"Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Palestine, State of",
608+
"Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar",
609+
"Réunion", "Romania", "Russian Federation", "Rwanda", "Saint Barthélemy", "Saint Helena, Ascension and Tristan da Cunha", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin (French part)", "Saint Pierre and Miquelon",
610+
"Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
611+
"Sint Maarten (Dutch part)", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Sudan", "Spain", "Sri Lanka",
612+
"Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of",
613+
"Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands",
614+
"Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu",
615+
"Venezuela, Bolivarian Republic of", "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe",
616+
}
617+
618+
selection, err := cliui.Select(inv, cliui.SelectOptions{
619+
Options: countries,
620+
Message: "Select the country:",
621+
HideSearch: false,
622+
})
623+
if err != nil {
624+
return "", xerrors.Errorf("select country: %w", err)
625+
}
626+
return selection, nil
627+
}

cli/login_test.go

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,58 @@ func TestLogin(t *testing.T) {
9696
"password", coderdtest.FirstUserParams.Password,
9797
"password", coderdtest.FirstUserParams.Password, // confirm
9898
"trial", "yes",
99+
"firstName", coderdtest.TrialUserParams.FirstName,
100+
"lastName", coderdtest.TrialUserParams.LastName,
101+
"phoneNumber", coderdtest.TrialUserParams.PhoneNumber,
102+
"jobTitle", coderdtest.TrialUserParams.JobTitle,
103+
"companyName", coderdtest.TrialUserParams.CompanyName,
104+
// `developers` and `country` `cliui.Select` automatically selects the first option during tests.
105+
}
106+
for i := 0; i < len(matches); i += 2 {
107+
match := matches[i]
108+
value := matches[i+1]
109+
pty.ExpectMatch(match)
110+
pty.WriteLine(value)
111+
}
112+
pty.ExpectMatch("Welcome to Coder")
113+
<-doneChan
114+
ctx := testutil.Context(t, testutil.WaitShort)
115+
resp, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
116+
Email: coderdtest.FirstUserParams.Email,
117+
Password: coderdtest.FirstUserParams.Password,
118+
})
119+
require.NoError(t, err)
120+
client.SetSessionToken(resp.SessionToken)
121+
me, err := client.User(ctx, codersdk.Me)
122+
require.NoError(t, err)
123+
assert.Equal(t, coderdtest.FirstUserParams.Username, me.Username)
124+
assert.Equal(t, coderdtest.FirstUserParams.Name, me.Name)
125+
assert.Equal(t, coderdtest.FirstUserParams.Email, me.Email)
126+
})
127+
128+
t.Run("InitialUserTTYWithNoTrial", func(t *testing.T) {
129+
t.Parallel()
130+
client := coderdtest.New(t, nil)
131+
// The --force-tty flag is required on Windows, because the `isatty` library does not
132+
// accurately detect Windows ptys when they are not attached to a process:
133+
// https://github.com/mattn/go-isatty/issues/59
134+
doneChan := make(chan struct{})
135+
root, _ := clitest.New(t, "login", "--force-tty", client.URL.String())
136+
pty := ptytest.New(t).Attach(root)
137+
go func() {
138+
defer close(doneChan)
139+
err := root.Run()
140+
assert.NoError(t, err)
141+
}()
142+
143+
matches := []string{
144+
"first user?", "yes",
145+
"username", coderdtest.FirstUserParams.Username,
146+
"name", coderdtest.FirstUserParams.Name,
147+
"email", coderdtest.FirstUserParams.Email,
148+
"password", coderdtest.FirstUserParams.Password,
149+
"password", coderdtest.FirstUserParams.Password, // confirm
150+
"trial", "no",
99151
}
100152
for i := 0; i < len(matches); i += 2 {
101153
match := matches[i]
@@ -142,6 +194,12 @@ func TestLogin(t *testing.T) {
142194
"password", coderdtest.FirstUserParams.Password,
143195
"password", coderdtest.FirstUserParams.Password, // confirm
144196
"trial", "yes",
197+
"firstName", coderdtest.TrialUserParams.FirstName,
198+
"lastName", coderdtest.TrialUserParams.LastName,
199+
"phoneNumber", coderdtest.TrialUserParams.PhoneNumber,
200+
"jobTitle", coderdtest.TrialUserParams.JobTitle,
201+
"companyName", coderdtest.TrialUserParams.CompanyName,
202+
// `developers` and `country` `cliui.Select` automatically selects the first option during tests.
145203
}
146204
for i := 0; i < len(matches); i += 2 {
147205
match := matches[i]
@@ -185,6 +243,12 @@ func TestLogin(t *testing.T) {
185243
"password", coderdtest.FirstUserParams.Password,
186244
"password", coderdtest.FirstUserParams.Password, // confirm
187245
"trial", "yes",
246+
"firstName", coderdtest.TrialUserParams.FirstName,
247+
"lastName", coderdtest.TrialUserParams.LastName,
248+
"phoneNumber", coderdtest.TrialUserParams.PhoneNumber,
249+
"jobTitle", coderdtest.TrialUserParams.JobTitle,
250+
"companyName", coderdtest.TrialUserParams.CompanyName,
251+
// `developers` and `country` `cliui.Select` automatically selects the first option during tests.
188252
}
189253
for i := 0; i < len(matches); i += 2 {
190254
match := matches[i]
@@ -217,6 +281,13 @@ func TestLogin(t *testing.T) {
217281
"--first-user-email", coderdtest.FirstUserParams.Email,
218282
"--first-user-password", coderdtest.FirstUserParams.Password,
219283
"--first-user-trial",
284+
"--first-user-first-name", coderdtest.TrialUserParams.FirstName,
285+
"--first-user-last-name", coderdtest.TrialUserParams.LastName,
286+
"--first-user-phone-number", coderdtest.TrialUserParams.PhoneNumber,
287+
"--first-user-job-title", coderdtest.TrialUserParams.JobTitle,
288+
"--first-user-company-name", coderdtest.TrialUserParams.CompanyName,
289+
"--first-user-country", coderdtest.TrialUserParams.Country,
290+
"--first-user-developers", coderdtest.TrialUserParams.Developers,
220291
)
221292
pty := ptytest.New(t).Attach(inv)
222293
w := clitest.StartWithWaiter(t, inv)
@@ -245,6 +316,14 @@ func TestLogin(t *testing.T) {
245316
"--first-user-email", coderdtest.FirstUserParams.Email,
246317
"--first-user-password", coderdtest.FirstUserParams.Password,
247318
"--first-user-trial",
319+
"--first-user-first-name", coderdtest.TrialUserParams.FirstName,
320+
"--first-user-last-name", coderdtest.TrialUserParams.LastName,
321+
"--first-user-phone-number", coderdtest.TrialUserParams.PhoneNumber,
322+
"--first-user-job-title", coderdtest.TrialUserParams.JobTitle,
323+
"--first-user-company-name", coderdtest.TrialUserParams.CompanyName,
324+
"--first-user-country", coderdtest.TrialUserParams.Country,
325+
"--first-user-developers", coderdtest.TrialUserParams.Developers,
326+
// `developers` and `country` `cliui.Select` automatically selects the first option during tests.
248327
)
249328
pty := ptytest.New(t).Attach(inv)
250329
w := clitest.StartWithWaiter(t, inv)
@@ -299,12 +378,21 @@ func TestLogin(t *testing.T) {
299378
// Validate that we reprompt for matching passwords.
300379
pty.ExpectMatch("Passwords do not match")
301380
pty.ExpectMatch("Enter a " + pretty.Sprint(cliui.DefaultStyles.Field, "password"))
302-
303381
pty.WriteLine(coderdtest.FirstUserParams.Password)
304382
pty.ExpectMatch("Confirm")
305383
pty.WriteLine(coderdtest.FirstUserParams.Password)
306384
pty.ExpectMatch("trial")
307385
pty.WriteLine("yes")
386+
pty.ExpectMatch("firstName")
387+
pty.WriteLine(coderdtest.TrialUserParams.FirstName)
388+
pty.ExpectMatch("lastName")
389+
pty.WriteLine(coderdtest.TrialUserParams.LastName)
390+
pty.ExpectMatch("phoneNumber")
391+
pty.WriteLine(coderdtest.TrialUserParams.PhoneNumber)
392+
pty.ExpectMatch("jobTitle")
393+
pty.WriteLine(coderdtest.TrialUserParams.JobTitle)
394+
pty.ExpectMatch("companyName")
395+
pty.WriteLine(coderdtest.TrialUserParams.CompanyName)
308396
pty.ExpectMatch("Welcome to Coder")
309397
<-doneChan
310398
})

cli/testdata/coder_login_--help.golden

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,38 @@ USAGE:
66
Authenticate with Coder deployment
77

88
OPTIONS:
9+
--first-user-company-name string, $CODER_FIRST_USER_COMPANY_NAME
10+
Specifies the company name of the user.
11+
12+
--first-user-country string, $CODER_FIRST_USER_COUNTRY
13+
Specifies the country of the user.
14+
15+
--first-user-developers string, $CODER_FIRST_USER_DEVELOPERS
16+
Specifies the number of developers.
17+
918
--first-user-email string, $CODER_FIRST_USER_EMAIL
1019
Specifies an email address to use if creating the first user for the
1120
deployment.
1221

22+
--first-user-first-name string, $CODER_FIRST_USER_FIRST_NAME
23+
Specifies the first name of the user.
24+
1325
--first-user-full-name string, $CODER_FIRST_USER_FULL_NAME
1426
Specifies a human-readable name for the first user of the deployment.
1527

28+
--first-user-job-title string, $CODER_FIRST_USER_JOB_TITLE
29+
Specifies the job title of the user.
30+
31+
--first-user-last-name string, $CODER_FIRST_USER_LAST_NAME
32+
Specifies the last name of the user.
33+
1634
--first-user-password string, $CODER_FIRST_USER_PASSWORD
1735
Specifies a password to use if creating the first user for the
1836
deployment.
1937

38+
--first-user-phone-number string, $CODER_FIRST_USER_PHONE_NUMBER
39+
Specifies the phone number of the user.
40+
2041
--first-user-trial bool, $CODER_FIRST_USER_TRIAL
2142
Specifies whether a trial license should be provisioned for the Coder
2243
deployment or not.

coderd/coderdtest/coderdtest.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,16 @@ var FirstUserParams = codersdk.CreateFirstUserRequest{
651651
Name: "Test User",
652652
}
653653

654+
var TrialUserParams = codersdk.CreateFirstUserTrialInfo{
655+
FirstName: "John",
656+
LastName: "Doe",
657+
PhoneNumber: "9999999999",
658+
JobTitle: "Engineer",
659+
CompanyName: "Acme Inc",
660+
Country: "United States",
661+
Developers: "10-50",
662+
}
663+
654664
// CreateFirstUser creates a user with preset credentials and authenticates
655665
// with the passed in codersdk client.
656666
func CreateFirstUser(t testing.TB, client *codersdk.Client) codersdk.CreateFirstUserResponse {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy