-
Notifications
You must be signed in to change notification settings - Fork 978
feat(cli): extend duration to longer units #15040
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
Changes from 3 commits
93e2e47
617fda1
6ce2600
950c056
e294630
02cfb2b
c51cf20
256c085
12e2c2d
7723ae9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,7 @@ func (r *RootCmd) tokens() *serpent.Command { | |
|
||
func (r *RootCmd) createToken() *serpent.Command { | ||
var ( | ||
tokenLifetime time.Duration | ||
tokenLifetime string | ||
name string | ||
user string | ||
) | ||
|
@@ -63,8 +63,26 @@ func (r *RootCmd) createToken() *serpent.Command { | |
if user != "" { | ||
userID = user | ||
} | ||
|
||
var parsedLifetime time.Duration | ||
var err error | ||
|
||
if tokenLifetime == "" { | ||
tokenConfig, err := client.GetTokenConfig(inv.Context(), userID) | ||
if err != nil { | ||
return xerrors.Errorf("get token config: %w", err) | ||
} | ||
|
||
parsedLifetime = tokenConfig.MaxTokenLifetime | ||
} else { | ||
parsedLifetime, err = extendedParseDuration(tokenLifetime) | ||
if err != nil { | ||
return xerrors.Errorf("parse lifetime: %w", err) | ||
} | ||
Comment on lines
+78
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: given that we have the token config available, we can check if the requested lifetime is greater than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah you're completely right - makes sense I just changed it a bit to handle the config fetched ! 👌 |
||
} | ||
|
||
res, err := client.CreateToken(inv.Context(), userID, codersdk.CreateTokenRequest{ | ||
Lifetime: tokenLifetime, | ||
Lifetime: parsedLifetime, | ||
TokenName: name, | ||
}) | ||
if err != nil { | ||
|
@@ -82,8 +100,7 @@ func (r *RootCmd) createToken() *serpent.Command { | |
Flag: "lifetime", | ||
Env: "CODER_TOKEN_LIFETIME", | ||
Description: "Specify a duration for the lifetime of the token.", | ||
Default: (time.Hour * 24 * 30).String(), | ||
Value: serpent.DurationOf(&tokenLifetime), | ||
Value: serpent.StringOf(&tokenLifetime), | ||
}, | ||
{ | ||
Flag: "name", | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -181,6 +181,39 @@ func isDigit(s string) bool { | |||||||||
}) == -1 | ||||||||||
} | ||||||||||
|
||||||||||
// extendedParseDuration is a more lenient version of parseDuration that allows | ||||||||||
// for more flexible input formats. | ||||||||||
// It allows for some extra units: | ||||||||||
// - d (days) | ||||||||||
// - y (years) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may make sense to explicitly note that this will not take into account things like DST or leap years or all that other assorted wibbly wobbley timey wimey stuff.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My first though was about leap years too :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep - indeed we can document it. About the behavior are we good to keep it this way ? I felt like it can quickly become complicated with time saving, leap years etc.. otherwise - maybe we can do it in the follow-up PR in serpent if we want to use it everywhere. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, definitely not a must-have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm happy with a naive definition of days and years here. Adding DST / leap year awareness adds a lot of complications, as you need both a timezone and a relative time. We can add a follow-up issue if we think it's necessary though. |
||||||||||
func extendedParseDuration(raw string) (time.Duration, error) { | ||||||||||
var duration time.Duration | ||||||||||
var err error | ||||||||||
|
||||||||||
switch { | ||||||||||
case strings.HasSuffix(raw, "d"): | ||||||||||
nbDays, err := strconv.Atoi(strings.TrimSuffix(raw, "d")) | ||||||||||
if err != nil { | ||||||||||
return 0, xerrors.Errorf("invalid duration: %q", raw) | ||||||||||
} | ||||||||||
duration = time.Duration(nbDays) * 24 * time.Hour | ||||||||||
case strings.HasSuffix(raw, "y"): | ||||||||||
nbYears, err := strconv.Atoi(strings.TrimSuffix(raw, "y")) | ||||||||||
if err != nil { | ||||||||||
return 0, xerrors.Errorf("invalid duration: %q", raw) | ||||||||||
} | ||||||||||
duration = time.Duration(nbYears) * 365 * 24 * time.Hour | ||||||||||
default: | ||||||||||
duration, err = time.ParseDuration(raw) | ||||||||||
} | ||||||||||
|
||||||||||
if err != nil { | ||||||||||
return 0, xerrors.Errorf("invalid duration: %q", raw) | ||||||||||
} | ||||||||||
return duration, nil | ||||||||||
} | ||||||||||
|
||||||||||
// parseTime attempts to parse a time (no date) from the given string using a number of layouts. | ||||||||||
// parseTime attempts to parse a time (no date) from the given string using a number of layouts. | ||||||||||
func parseTime(s string) (time.Time, error) { | ||||||||||
// Try a number of possible layouts. | ||||||||||
|
defelmnq marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,34 @@ func TestDurationDisplay(t *testing.T) { | |
} | ||
} | ||
|
||
func TestExtendedParseDuration(t *testing.T) { | ||
t.Parallel() | ||
for _, testCase := range []struct { | ||
Duration string | ||
Expected time.Duration | ||
ExpectedOk bool | ||
}{ | ||
{"1d", 24 * time.Hour, true}, | ||
{"1y", 365 * 24 * time.Hour, true}, | ||
defelmnq marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{"10s", 10 * time.Second, true}, | ||
{"1m", 1 * time.Minute, true}, | ||
{"20h", 20 * time.Hour, true}, | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{"10y10d10s", 0, false}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think users will expect durations like 1y1d to work here. It shouldn't be a huge lift to enable this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @johnstcn I tried to play with time.ParseDuration golang function to mimic the behavior as much as possible - and changed to indeed modify what you've raised. |
||
} { | ||
testCase := testCase | ||
t.Run(testCase.Duration, func(t *testing.T) { | ||
t.Parallel() | ||
actual, err := extendedParseDuration(testCase.Duration) | ||
if testCase.ExpectedOk { | ||
require.NoError(t, err) | ||
assert.Equal(t, testCase.Expected, actual) | ||
} else { | ||
assert.Error(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestRelative(t *testing.T) { | ||
t.Parallel() | ||
assert.Equal(t, relative(time.Minute), "in 1m") | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@defelmnq @johnstcn WDYT about defining a new type in coder/serpent,
time.LongDuration
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be done as a follow-up!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a follow-up issue here - would definitely be a cool one to do indeed.