Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitccbb687

Browse files
authored
feat(cli): extend duration to longer units (#15040)
This PR is a proposal to improve the situation described in#14750 For some precise commands - we would like to be able to use durationsbigger than hours, minutes..This PR extends the Duration proposed by Go with : - `d` - a day or 24hours.- `y` - a year or 365 days.I also removed the default value for lifetime and instead fetch themaxLifetime value from codersdk - so by default if no value set we usethe value defined in the config.
1 parent774c9dd commitccbb687

File tree

5 files changed

+144
-7
lines changed

5 files changed

+144
-7
lines changed

‎cli/testdata/coder_tokens_create_--help.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ USAGE:
66
Create a token
77

88
OPTIONS:
9-
--lifetimeduration, $CODER_TOKEN_LIFETIME (default: 720h0m0s)
9+
--lifetimestring, $CODER_TOKEN_LIFETIME
1010
Specify a duration for the lifetime of the token.
1111

1212
-n, --name string, $CODER_TOKEN_NAME

‎cli/tokens.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func (r *RootCmd) tokens() *serpent.Command {
4646

4747
func (r*RootCmd)createToken()*serpent.Command {
4848
var (
49-
tokenLifetimetime.Duration
49+
tokenLifetimestring
5050
namestring
5151
userstring
5252
)
@@ -63,8 +63,30 @@ func (r *RootCmd) createToken() *serpent.Command {
6363
ifuser!="" {
6464
userID=user
6565
}
66+
67+
varparsedLifetime time.Duration
68+
varerrerror
69+
70+
tokenConfig,err:=client.GetTokenConfig(inv.Context(),userID)
71+
iferr!=nil {
72+
returnxerrors.Errorf("get token config: %w",err)
73+
}
74+
75+
iftokenLifetime=="" {
76+
parsedLifetime=tokenConfig.MaxTokenLifetime
77+
}else {
78+
parsedLifetime,err=extendedParseDuration(tokenLifetime)
79+
iferr!=nil {
80+
returnxerrors.Errorf("parse lifetime: %w",err)
81+
}
82+
83+
ifparsedLifetime>tokenConfig.MaxTokenLifetime {
84+
returnxerrors.Errorf("lifetime (%s) is greater than the maximum allowed lifetime (%s)",parsedLifetime,tokenConfig.MaxTokenLifetime)
85+
}
86+
}
87+
6688
res,err:=client.CreateToken(inv.Context(),userID, codersdk.CreateTokenRequest{
67-
Lifetime:tokenLifetime,
89+
Lifetime:parsedLifetime,
6890
TokenName:name,
6991
})
7092
iferr!=nil {
@@ -82,8 +104,7 @@ func (r *RootCmd) createToken() *serpent.Command {
82104
Flag:"lifetime",
83105
Env:"CODER_TOKEN_LIFETIME",
84106
Description:"Specify a duration for the lifetime of the token.",
85-
Default: (time.Hour*24*30).String(),
86-
Value:serpent.DurationOf(&tokenLifetime),
107+
Value:serpent.StringOf(&tokenLifetime),
87108
},
88109
{
89110
Flag:"name",

‎cli/util.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"fmt"
5+
"regexp"
56
"strconv"
67
"strings"
78
"time"
@@ -181,6 +182,78 @@ func isDigit(s string) bool {
181182
})==-1
182183
}
183184

185+
// extendedParseDuration is a more lenient version of parseDuration that allows
186+
// for more flexible input formats and cumulative durations.
187+
// It allows for some extra units:
188+
// - d (days, interpreted as 24h)
189+
// - y (years, interpreted as 8_760h)
190+
//
191+
// FIXME: handle fractional values as discussed in https://github.com/coder/coder/pull/15040#discussion_r1799261736
192+
funcextendedParseDuration(rawstring) (time.Duration,error) {
193+
vardint64
194+
isPositive:=true
195+
196+
// handle negative durations by checking for a leading '-'
197+
ifstrings.HasPrefix(raw,"-") {
198+
raw=raw[1:]
199+
isPositive=false
200+
}
201+
202+
ifraw=="" {
203+
return0,xerrors.Errorf("invalid duration: %q",raw)
204+
}
205+
206+
// Regular expression to match any characters that do not match the expected duration format
207+
invalidCharRe:=regexp.MustCompile(`[^0-9|nsuµhdym]+`)
208+
ifinvalidCharRe.MatchString(raw) {
209+
return0,xerrors.Errorf("invalid duration format: %q",raw)
210+
}
211+
212+
// Regular expression to match numbers followed by 'd', 'y', or time units
213+
re:=regexp.MustCompile(`(-?\d+)(ns|us|µs|ms|s|m|h|d|y)`)
214+
matches:=re.FindAllStringSubmatch(raw,-1)
215+
216+
for_,match:=rangematches {
217+
varnumint64
218+
num,err:=strconv.ParseInt(match[1],10,0)
219+
iferr!=nil {
220+
return0,xerrors.Errorf("invalid duration: %q",match[1])
221+
}
222+
223+
switchmatch[2] {
224+
case"d":
225+
// we want to check if d + num * int64(24*time.Hour) would overflow
226+
ifd> (1<<63-1)-num*int64(24*time.Hour) {
227+
return0,xerrors.Errorf("invalid duration: %q",raw)
228+
}
229+
d+=num*int64(24*time.Hour)
230+
case"y":
231+
// we want to check if d + num * int64(8760*time.Hour) would overflow
232+
ifd> (1<<63-1)-num*int64(8760*time.Hour) {
233+
return0,xerrors.Errorf("invalid duration: %q",raw)
234+
}
235+
d+=num*int64(8760*time.Hour)
236+
case"h","m","s","ns","us","µs","ms":
237+
partDuration,err:=time.ParseDuration(match[0])
238+
iferr!=nil {
239+
return0,xerrors.Errorf("invalid duration: %q",match[0])
240+
}
241+
ifd> (1<<63-1)-int64(partDuration) {
242+
return0,xerrors.Errorf("invalid duration: %q",raw)
243+
}
244+
d+=int64(partDuration)
245+
default:
246+
return0,xerrors.Errorf("invalid duration unit: %q",match[2])
247+
}
248+
}
249+
250+
if!isPositive {
251+
return-time.Duration(d),nil
252+
}
253+
254+
returntime.Duration(d),nil
255+
}
256+
184257
// parseTime attempts to parse a time (no date) from the given string using a number of layouts.
185258
funcparseTime(sstring) (time.Time,error) {
186259
// Try a number of possible layouts.

‎cli/util_internal_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,50 @@ func TestDurationDisplay(t *testing.T) {
4141
}
4242
}
4343

44+
funcTestExtendedParseDuration(t*testing.T) {
45+
t.Parallel()
46+
for_,testCase:=range []struct {
47+
Durationstring
48+
Expected time.Duration
49+
ExpectedOkbool
50+
}{
51+
{"1d",24*time.Hour,true},
52+
{"1y",365*24*time.Hour,true},
53+
{"10s",10*time.Second,true},
54+
{"1m",1*time.Minute,true},
55+
{"20h",20*time.Hour,true},
56+
{"10y10d10s",10*365*24*time.Hour+10*24*time.Hour+10*time.Second,true},
57+
{"10ms",10*time.Millisecond,true},
58+
{"5y10d10s5y2ms8ms",10*365*24*time.Hour+10*24*time.Hour+10*time.Second+10*time.Millisecond,true},
59+
{"10yz10d10s",0,false},
60+
{"1µs2h1d",1*time.Microsecond+2*time.Hour+1*24*time.Hour,true},
61+
{"1y365d",2*365*24*time.Hour,true},
62+
{"1µs10us",1*time.Microsecond+10*time.Microsecond,true},
63+
// negative related tests
64+
{"-",0,false},
65+
{"-2h10m",-2*time.Hour-10*time.Minute,true},
66+
{"--10s",0,false},
67+
{"10s-10m",0,false},
68+
// overflow related tests
69+
{"-20000000000000h",0,false},
70+
{"92233754775807y",0,false},
71+
{"200y200y200y200y200y",0,false},
72+
{"9223372036854775807s",0,false},
73+
} {
74+
testCase:=testCase
75+
t.Run(testCase.Duration,func(t*testing.T) {
76+
t.Parallel()
77+
actual,err:=extendedParseDuration(testCase.Duration)
78+
iftestCase.ExpectedOk {
79+
require.NoError(t,err)
80+
assert.Equal(t,testCase.Expected,actual)
81+
}else {
82+
assert.Error(t,err)
83+
}
84+
})
85+
}
86+
}
87+
4488
funcTestRelative(t*testing.T) {
4589
t.Parallel()
4690
assert.Equal(t,relative(time.Minute),"in 1m")

‎docs/reference/cli/tokens_create.md

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp