Skip to content

Commit f8f0b2a

Browse files
authored
improve support for parsing time durations with 'day' units (#3599)
* custom duration type for "cscli decisions list", "cscli alerts list" * custom duration type for "cscli allowlist add" * custom duration type for "cscli machines prune" * custom duration type for "cscli bouncers prune" * replace old function ParseDuration * use custom duration type in expr helpers * update dependency * lint * test fix * support days in 'metrics_max_age' * DurationWithDays for 'max_age'
1 parent d10067e commit f8f0b2a

File tree

21 files changed

+153
-202
lines changed

21 files changed

+153
-202
lines changed

cmd/crowdsec-cli/clialert/alerts.go

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import (
1212
"strconv"
1313
"strings"
1414
"text/template"
15+
"time"
1516

1617
"github.com/fatih/color"
1718
"github.com/go-openapi/strfmt"
1819
log "github.com/sirupsen/logrus"
1920
"github.com/spf13/cobra"
2021
"gopkg.in/yaml.v3"
2122

23+
"github.com/crowdsecurity/go-cs-lib/cstime"
2224
"github.com/crowdsecurity/go-cs-lib/maptools"
2325

2426
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
@@ -247,34 +249,6 @@ func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.Alerts
247249
alertListFilter.Limit = limit
248250
}
249251

250-
if *alertListFilter.Until == "" {
251-
alertListFilter.Until = nil
252-
} else if strings.HasSuffix(*alertListFilter.Until, "d") {
253-
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
254-
realDuration := strings.TrimSuffix(*alertListFilter.Until, "d")
255-
256-
days, err := strconv.Atoi(realDuration)
257-
if err != nil {
258-
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Until)
259-
}
260-
261-
*alertListFilter.Until = fmt.Sprintf("%d%s", days*24, "h")
262-
}
263-
264-
if *alertListFilter.Since == "" {
265-
alertListFilter.Since = nil
266-
} else if strings.HasSuffix(*alertListFilter.Since, "d") {
267-
// time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier
268-
realDuration := strings.TrimSuffix(*alertListFilter.Since, "d")
269-
270-
days, err := strconv.Atoi(realDuration)
271-
if err != nil {
272-
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Since)
273-
}
274-
275-
*alertListFilter.Since = fmt.Sprintf("%d%s", days*24, "h")
276-
}
277-
278252
if *alertListFilter.IncludeCAPI {
279253
*alertListFilter.Limit = 0
280254
}
@@ -330,8 +304,8 @@ func (cli *cliAlerts) newListCmd() *cobra.Command {
330304
ScenarioEquals: new(string),
331305
IPEquals: new(string),
332306
RangeEquals: new(string),
333-
Since: new(string),
334-
Until: new(string),
307+
Since: cstime.DurationWithDays(0),
308+
Until: cstime.DurationWithDays(0),
335309
TypeEquals: new(string),
336310
IncludeCAPI: new(bool),
337311
OriginEquals: new(string),
@@ -362,8 +336,8 @@ cscli alerts list --type ban`,
362336
flags := cmd.Flags()
363337
flags.SortFlags = false
364338
flags.BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
365-
flags.StringVar(alertListFilter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
366-
flags.StringVar(alertListFilter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
339+
flags.Var(&alertListFilter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)")
340+
flags.Var(&alertListFilter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)")
367341
flags.StringVarP(alertListFilter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
368342
flags.StringVarP(alertListFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
369343
flags.StringVarP(alertListFilter.RangeEquals, "range", "r", "", "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)")
@@ -560,10 +534,9 @@ func (cli *cliAlerts) newInspectCmd() *cobra.Command {
560534
}
561535

562536
func (cli *cliAlerts) newFlushCmd() *cobra.Command {
563-
var (
564-
maxItems int
565-
maxAge string
566-
)
537+
var maxItems int
538+
539+
maxAge := cstime.DurationWithDays(7*24*time.Hour)
567540

568541
cmd := &cobra.Command{
569542
Use: `flush`,
@@ -584,7 +557,7 @@ func (cli *cliAlerts) newFlushCmd() *cobra.Command {
584557
return err
585558
}
586559
log.Info("Flushing alerts. !! This may take a long time !!")
587-
err = db.FlushAlerts(ctx, maxAge, maxItems)
560+
err = db.FlushAlerts(ctx, time.Duration(maxAge), maxItems)
588561
if err != nil {
589562
return fmt.Errorf("unable to flush alerts: %w", err)
590563
}
@@ -596,7 +569,7 @@ func (cli *cliAlerts) newFlushCmd() *cobra.Command {
596569

597570
cmd.Flags().SortFlags = false
598571
cmd.Flags().IntVar(&maxItems, "max-items", 5000, "Maximum number of alert items to keep in the database")
599-
cmd.Flags().StringVar(&maxAge, "max-age", "7d", "Maximum age of alert items to keep in the database")
572+
cmd.Flags().Var(&maxAge, "max-age", "Maximum age of alert items to keep in the database")
600573

601574
return cmd
602575
}

cmd/crowdsec-cli/cliallowlists/allowlists.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ func (cli *cliAllowLists) delete(ctx context.Context, db *database.Client, name
399399

400400
func (cli *cliAllowLists) newAddCmd() *cobra.Command {
401401
var (
402-
expirationStr string
403-
comment string
402+
expiration cstime.DurationWithDays
403+
comment string
404404
)
405405

406406
cmd := &cobra.Command{
@@ -424,25 +424,16 @@ func (cli *cliAllowLists) newAddCmd() *cobra.Command {
424424
return err
425425
}
426426

427-
var expiration time.Duration
428-
429-
if expirationStr != "" {
430-
expiration, err = cstime.ParseDuration(expirationStr)
431-
if err != nil {
432-
return err
433-
}
434-
}
435-
436427
name := args[0]
437428
values := args[1:]
438429

439-
return cli.add(ctx, db, name, values, expiration, comment)
430+
return cli.add(ctx, db, name, values, time.Duration(expiration), comment)
440431
},
441432
}
442433

443434
flags := cmd.Flags()
444435

445-
flags.StringVarP(&expirationStr, "expiration", "e", "", "expiration duration")
436+
flags.VarP(&expiration, "expiration", "e", "expiration duration")
446437
flags.StringVarP(&comment, "comment", "d", "", "comment for the value")
447438

448439
return cmd

cmd/crowdsec-cli/clibouncer/prune.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ import (
99
"github.com/fatih/color"
1010
"github.com/spf13/cobra"
1111

12+
"github.com/crowdsecurity/go-cs-lib/cstime"
13+
1214
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
1315
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/ask"
1416
)
1517

18+
const defaultPruneDuration = 60 * time.Minute
19+
1620
func (cli *cliBouncers) prune(ctx context.Context, duration time.Duration, force bool) error {
1721
if duration < 2*time.Minute {
1822
if yes, err := ask.YesNo(
@@ -59,12 +63,9 @@ func (cli *cliBouncers) prune(ctx context.Context, duration time.Duration, force
5963
}
6064

6165
func (cli *cliBouncers) newPruneCmd() *cobra.Command {
62-
var (
63-
duration time.Duration
64-
force bool
65-
)
66+
var force bool
6667

67-
const defaultDuration = 60 * time.Minute
68+
duration := cstime.DurationWithDays(defaultPruneDuration)
6869

6970
cmd := &cobra.Command{
7071
Use: "prune",
@@ -74,12 +75,12 @@ func (cli *cliBouncers) newPruneCmd() *cobra.Command {
7475
Example: `cscli bouncers prune -d 45m
7576
cscli bouncers prune -d 45m --force`,
7677
RunE: func(cmd *cobra.Command, _ []string) error {
77-
return cli.prune(cmd.Context(), duration, force)
78+
return cli.prune(cmd.Context(), time.Duration(duration), force)
7879
},
7980
}
8081

8182
flags := cmd.Flags()
82-
flags.DurationVarP(&duration, "duration", "d", defaultDuration, "duration of time since last pull")
83+
flags.VarP(&duration, "duration", "d", "duration of time since last pull")
8384
flags.BoolVar(&force, "force", false, "force prune without asking for confirmation")
8485

8586
return cmd

cmd/crowdsec-cli/clidecision/decisions.go

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
2424
"github.com/crowdsecurity/crowdsec/pkg/models"
2525
"github.com/crowdsecurity/crowdsec/pkg/types"
26+
27+
"github.com/crowdsecurity/go-cs-lib/cstime"
2628
)
2729

2830
type configGetter func() *csconfig.Config
@@ -184,34 +186,8 @@ func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOp
184186
if noSimu != nil && *noSimu {
185187
filter.IncludeSimulated = new(bool)
186188
}
187-
/* nullify the empty entries to avoid bad filter */
188-
if *filter.Until == "" {
189-
filter.Until = nil
190-
} else if strings.HasSuffix(*filter.Until, "d") {
191-
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
192-
realDuration := strings.TrimSuffix(*filter.Until, "d")
193-
194-
days, err := strconv.Atoi(realDuration)
195-
if err != nil {
196-
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
197-
}
198-
199-
*filter.Until = fmt.Sprintf("%d%s", days*24, "h")
200-
}
201189

202-
if *filter.Since == "" {
203-
filter.Since = nil
204-
} else if strings.HasSuffix(*filter.Since, "d") {
205-
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
206-
realDuration := strings.TrimSuffix(*filter.Since, "d")
207-
208-
days, err := strconv.Atoi(realDuration)
209-
if err != nil {
210-
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Since)
211-
}
212-
213-
*filter.Since = fmt.Sprintf("%d%s", days*24, "h")
214-
}
190+
/* nullify the empty entries to avoid bad filter */
215191

216192
if *filter.IncludeCAPI {
217193
*filter.Limit = 0
@@ -270,8 +246,8 @@ func (cli *cliDecisions) newListCmd() *cobra.Command {
270246
OriginEquals: new(string),
271247
IPEquals: new(string),
272248
RangeEquals: new(string),
273-
Since: new(string),
274-
Until: new(string),
249+
Since: cstime.DurationWithDays(0),
250+
Until: cstime.DurationWithDays(0),
275251
TypeEquals: new(string),
276252
IncludeCAPI: new(bool),
277253
Limit: new(int),
@@ -300,8 +276,8 @@ cscli decisions list --origin lists --scenario list_name
300276
flags := cmd.Flags()
301277
flags.SortFlags = false
302278
flags.BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
303-
flags.StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
304-
flags.StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
279+
flags.Var(&filter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)")
280+
flags.Var(&filter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)")
305281
flags.StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
306282
flags.StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
307283
flags.StringVar(filter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))

cmd/crowdsec-cli/climachine/prune.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ import (
99
"github.com/fatih/color"
1010
"github.com/spf13/cobra"
1111

12+
"github.com/crowdsecurity/go-cs-lib/cstime"
13+
1214
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
1315
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/ask"
1416
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
1517
)
1618

19+
const defaultPruneDuration = 10 * time.Minute
20+
1721
func (cli *cliMachines) prune(ctx context.Context, duration time.Duration, notValidOnly bool, force bool) error {
1822
if duration < 2*time.Minute && !notValidOnly {
1923
if yes, err := ask.YesNo(
@@ -67,12 +71,11 @@ func (cli *cliMachines) prune(ctx context.Context, duration time.Duration, notVa
6771

6872
func (cli *cliMachines) newPruneCmd() *cobra.Command {
6973
var (
70-
duration time.Duration
7174
notValidOnly bool
7275
force bool
7376
)
7477

75-
const defaultDuration = 10 * time.Minute
78+
duration := cstime.DurationWithDays(defaultPruneDuration)
7679

7780
cmd := &cobra.Command{
7881
Use: "prune",
@@ -84,12 +87,12 @@ cscli machines prune --not-validated-only --force`,
8487
Args: args.NoArgs,
8588
DisableAutoGenTag: true,
8689
RunE: func(cmd *cobra.Command, _ []string) error {
87-
return cli.prune(cmd.Context(), duration, notValidOnly, force)
90+
return cli.prune(cmd.Context(), time.Duration(duration), notValidOnly, force)
8891
},
8992
}
9093

9194
flags := cmd.Flags()
92-
flags.DurationVarP(&duration, "duration", "d", defaultDuration, "duration of time since validated machine last heartbeat")
95+
flags.VarP(&duration, "duration", "d", "duration of time since validated machine last heartbeat")
9396
flags.BoolVar(&notValidOnly, "not-validated-only", false, "only prune machines that are not validated")
9497
flags.BoolVar(&force, "force", false, "force prune without asking for confirmation")
9598

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ require (
2424
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
2525
github.com/creack/pty v1.1.21 // indirect
2626
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
27-
github.com/crowdsecurity/go-cs-lib v0.0.18
27+
github.com/crowdsecurity/go-cs-lib v0.0.19
2828
github.com/crowdsecurity/grokky v0.2.2
2929
github.com/crowdsecurity/machineid v1.0.2
3030
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@@ -87,8 +87,8 @@ require (
8787
github.com/shirou/gopsutil/v3 v3.23.5
8888
github.com/sirupsen/logrus v1.9.3
8989
github.com/slack-go/slack v0.16.0
90-
github.com/spf13/cobra v1.8.1
91-
github.com/spf13/pflag v1.0.5 // indirect
90+
github.com/spf13/cobra v1.9.1
91+
github.com/spf13/pflag v1.0.6 // indirect
9292
github.com/stretchr/testify v1.10.0
9393
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
9494
github.com/wasilibs/go-re2 v1.7.0
@@ -100,7 +100,7 @@ require (
100100
go.opentelemetry.io/otel/trace v1.28.0 // indirect
101101
golang.org/x/crypto v0.36.0
102102
golang.org/x/mod v0.23.0
103-
golang.org/x/net v0.38.0 // indirect
103+
golang.org/x/net v0.38.0
104104
golang.org/x/sync v0.12.0
105105
golang.org/x/sys v0.31.0
106106
golang.org/x/text v0.23.0
@@ -131,7 +131,7 @@ require (
131131
github.com/bytedance/sonic/loader v0.2.1 // indirect
132132
github.com/cloudwego/base64x v0.1.4 // indirect
133133
github.com/cloudwego/iasm v0.2.0 // indirect
134-
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
134+
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
135135
github.com/felixge/httpsnoop v1.0.4 // indirect
136136
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
137137
github.com/gin-contrib/sse v0.1.0 // indirect

go.sum

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
100100
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
101101
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
102102
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
103-
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
104-
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
103+
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
104+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
105105
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
106106
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
107107
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -111,8 +111,8 @@ github.com/crowdsecurity/coraza/v3 v3.0.0-20250320231801-749b8bded21a h1:2Nyr+47
111111
github.com/crowdsecurity/coraza/v3 v3.0.0-20250320231801-749b8bded21a/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA=
112112
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
113113
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
114-
github.com/crowdsecurity/go-cs-lib v0.0.18 h1:GNyvaag5MXfuapIy4E30pIOvIE5AyHoanJBNSMA1cmE=
115-
github.com/crowdsecurity/go-cs-lib v0.0.18/go.mod h1:XwGcvTt4lMq4Tm1IRMSKMDf0CVrnytTU8Uoofa7AR+g=
114+
github.com/crowdsecurity/go-cs-lib v0.0.19 h1:wA4O8hGrEntTGn7eZTJqnQ3mrAje5JvQAj8DNbe5IZg=
115+
github.com/crowdsecurity/go-cs-lib v0.0.19/go.mod h1:hz2FOHFXc0vWzH78uxo2VebtPQ9Snkbdzy3TMA20tVQ=
116116
github.com/crowdsecurity/grokky v0.2.2 h1:yALsI9zqpDArYzmSSxfBq2dhYuGUTKMJq8KOEIAsuo4=
117117
github.com/crowdsecurity/grokky v0.2.2/go.mod h1:33usDIYzGDsgX1kHAThCbseso6JuWNJXOzRQDGXHtWM=
118118
github.com/crowdsecurity/machineid v1.0.2 h1:wpkpsUghJF8Khtmn/tg6GxgdhLA1Xflerh5lirI+bdc=
@@ -660,11 +660,12 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
660660
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
661661
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
662662
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
663-
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
664-
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
663+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
664+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
665665
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
666-
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
667666
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
667+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
668+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
668669
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
669670
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
670671
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=

0 commit comments

Comments
 (0)