cmd/uplink: better usability for date specification

I use `uplink share` command but I always fail to set the --not-before parameter.

 * Usually I try +2d when I see in the help that +2h is possible --> fail
 * When it fails, I try to set explicit date, like 2012-12-23 --> fail

This patch makes it possible to use:

 * day duration (like +3d)
 * shorter date definition (like `2023-12-12` or `2023-12-12T12:40`)

Change-Id: I2243b36f59c8929eb0473c4bb4fed19220890c71
This commit is contained in:
Márton Elek 2023-02-17 14:17:18 +01:00
parent 63fa386b0a
commit 6737d427e4
No known key found for this signature in database
3 changed files with 109 additions and 13 deletions

View File

@ -60,10 +60,10 @@ func (ap *accessPermissions) Setup(params clingy.Parameters, prefixFlags bool) {
ap.notBefore = params.Flag("not-before",
"Disallow access before this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')",
nil, clingy.Transform(parseHumanDate), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
nil, clingy.Transform(parseHumanDateNotBefore), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
ap.notAfter = params.Flag("not-after",
"Disallow access after this time (e.g. '+2h', 'now', '2020-01-02T15:04:05Z0700', 'none')",
nil, clingy.Transform(parseHumanDate), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
nil, clingy.Transform(parseHumanDateNotAfter), clingy.Type("relative_date"), clingy.Optional).(*time.Time)
if !prefixFlags {
ap.prefixes = params.Arg("prefix", "Key prefix access will be restricted to",

View File

@ -6,6 +6,8 @@ package main
import (
"encoding/json"
"flag"
"regexp"
"strconv"
"time"
"github.com/zeebo/clingy"
@ -41,6 +43,24 @@ func (s *stdlibFlags) Setup(f clingy.Flags) {
// parseHumanDate parses command-line flags which accept relative and absolute datetimes.
// It can be passed to clingy.Transform to create a clingy.Option.
func parseHumanDate(date string) (time.Time, error) {
return parseHumanDateInLocation(date, time.Now().Location(), false)
}
// parseHumanDateNotBefore parses command-line flags which accept relative and absolute datetimes.
func parseHumanDateNotBefore(date string) (time.Time, error) {
return parseHumanDateInLocation(date, time.Now().Location(), false)
}
// parseHumanDateNotAfter parses relative/short date times. But it rounds up the period.
// For example parseHumanDateNotAfter('2022-01-23') will return with '2022-01-23T23:59...' (end of day),
// and parseHumanDateNotAfter('2022-01-23T15:04') will return with '2022-01-23T15:04:59...' (end of minute)...
func parseHumanDateNotAfter(date string) (time.Time, error) {
return parseHumanDateInLocation(date, time.Now().Location(), true)
}
var durationWithDay = regexp.MustCompile(`(\+|-)(\d+)d`)
func parseHumanDateInLocation(date string, loc *time.Location, ceil bool) (time.Time, error) {
switch {
case date == "none":
return time.Time{}, nil
@ -49,17 +69,53 @@ func parseHumanDate(date string) (time.Time, error) {
case date == "now":
return time.Now(), nil
case date[0] == '+' || date[0] == '-':
dayDuration := durationWithDay.FindStringSubmatch(date)
if len(dayDuration) > 0 {
days, _ := strconv.Atoi(dayDuration[2])
if dayDuration[1] == "-" {
days *= -1
}
return time.Now().Add(time.Hour * time.Duration(days*24)), nil
}
d, err := time.ParseDuration(date)
return time.Now().Add(d), errs.Wrap(err)
default:
t, err := time.Parse(time.RFC3339, date)
if err != nil {
d, err := time.ParseDuration(date)
if err == nil {
return time.Now().Add(d), nil
}
t, err := time.ParseInLocation(time.RFC3339, date, time.Now().Location())
if err == nil {
return t, nil
}
return t, errs.Wrap(err)
// shorter version of RFC3339
t, err = time.ParseInLocation("2006-01-02T15:04:05", date, loc)
if err == nil {
if ceil {
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()+1, -1, loc)
}
return t, nil
}
t, err = time.ParseInLocation("2006-01-02T15:04", date, loc)
if err == nil {
if ceil {
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()+1, 0, -1, loc)
}
return t, nil
}
t, err = time.ParseInLocation("2006-01-02", date, loc)
if err == nil {
if ceil {
t = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, -1, loc)
}
return t, nil
}
d, err := time.ParseDuration(date)
if err == nil {
return time.Now().Add(d), nil
}
return time.Time{}, err
}
}

View File

@ -11,21 +11,61 @@ import (
)
func TestParseHumanDate(t *testing.T) {
loc, err := time.LoadLocation("Asia/Tbilisi")
require.NoError(t, err)
t.Run("parse relative date", func(t *testing.T) {
parsed, err := parseHumanDate("+24h")
parsed, err := parseHumanDateNotBefore("+24h")
require.NoError(t, err)
require.Less(t, parsed.Unix(), time.Now().Add(25*time.Hour).Unix())
require.Greater(t, parsed.Unix(), time.Now().Add(23*time.Hour).Unix())
})
t.Run("parse absolute date", func(t *testing.T) {
parsed, err := parseHumanDate("2030-02-03T12:13:14+01:00")
t.Run("parse relative date with day", func(t *testing.T) {
parsed, err := parseHumanDateNotBefore("+13d")
require.NoError(t, err)
require.Less(t, parsed.Unix(), time.Now().Add((13*24+1)*time.Hour).Unix())
require.Greater(t, parsed.Unix(), time.Now().Add((13*24-1)*time.Hour).Unix())
})
t.Run("parse absolute full date", func(t *testing.T) {
parsed, err := parseHumanDateNotBefore("2030-02-03T12:13:14+01:00")
require.NoError(t, err)
require.Equal(t, "2030-02-03T12:13:14+01:00", parsed.Format(time.RFC3339))
})
t.Run("parse absolute date without TZ", func(t *testing.T) {
parsed, err := parseHumanDateInLocation("2030-02-03T12:13:14", loc, false)
require.NoError(t, err)
require.Equal(t, "2030-02-03T12:13:14+04:00", parsed.Format(time.RFC3339))
parsed, err = parseHumanDateInLocation("2030-02-03T12:13:14", loc, true)
require.NoError(t, err)
require.Equal(t, "2030-02-03T12:13:14.999999999+04:00", parsed.Format(time.RFC3339Nano))
})
t.Run("parse absolute date without sec", func(t *testing.T) {
parsed, err := parseHumanDateInLocation("2030-02-03T12:13", loc, false)
require.NoError(t, err)
require.Equal(t, "2030-02-03T12:13:00+04:00", parsed.Format(time.RFC3339))
parsed, err = parseHumanDateInLocation("2030-02-03T12:13", loc, true)
require.NoError(t, err)
require.Equal(t, "2030-02-03T12:13:59.999999999+04:00", parsed.Format(time.RFC3339Nano))
})
t.Run("parse absolute date without hour", func(t *testing.T) {
parsed, err := parseHumanDateInLocation("2030-03-31", loc, false)
require.NoError(t, err)
require.Equal(t, "2030-03-31T00:00:00+04:00", parsed.Format(time.RFC3339))
parsed, err = parseHumanDateInLocation("2030-03-31", loc, true)
require.NoError(t, err)
require.Equal(t, "2030-03-31T23:59:59.999999999+04:00", parsed.Format(time.RFC3339Nano))
})
t.Run("parse nonsense", func(t *testing.T) {
parsed, err := parseHumanDate("999999")
parsed, err := parseHumanDateNotBefore("999999")
require.Equal(t, time.Time{}, parsed)
require.Error(t, err)
})