mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
Add date/time argument type. (#9817)
* Add date/time argument type. * Add an argument to select which time formats are valid. * Increase minimum date for epoch timestamps to avoid ambiguity.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -749,6 +750,135 @@ func (f *FlagSet) Var(value flag.Value, name, usage string) {
|
|||||||
f.flagSet.Var(value, name, usage)
|
f.flagSet.Var(value, name, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- TimeVar and timeValue
|
||||||
|
type TimeVar struct {
|
||||||
|
Name string
|
||||||
|
Aliases []string
|
||||||
|
Usage string
|
||||||
|
Default time.Time
|
||||||
|
Hidden bool
|
||||||
|
EnvVar string
|
||||||
|
Target *time.Time
|
||||||
|
Completion complete.Predictor
|
||||||
|
Formats TimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify the allowable formats, identified by the minimum
|
||||||
|
// precision accepted.
|
||||||
|
// TODO: move this somewhere where it can be re-used for the API.
|
||||||
|
type TimeFormat int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TimeVar_EpochSecond TimeFormat = 1 << iota
|
||||||
|
TimeVar_RFC3339Nano
|
||||||
|
TimeVar_RFC3339Second
|
||||||
|
TimeVar_Day
|
||||||
|
TimeVar_Month
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default value to use
|
||||||
|
const TimeVar_TimeOrDay TimeFormat = TimeVar_EpochSecond | TimeVar_RFC3339Nano | TimeVar_RFC3339Second | TimeVar_Day
|
||||||
|
|
||||||
|
// parseTimeAlternatives attempts several different allowable variants
|
||||||
|
// of the time field.
|
||||||
|
func parseTimeAlternatives(input string, allowedFormats TimeFormat) (time.Time, error) {
|
||||||
|
// The RFC3339 formats require the inclusion of a time zone.
|
||||||
|
if allowedFormats&TimeVar_RFC3339Nano != 0 {
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, input)
|
||||||
|
if err == nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowedFormats&TimeVar_RFC3339Second != 0 {
|
||||||
|
t, err := time.Parse(time.RFC3339, input)
|
||||||
|
if err == nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowedFormats&TimeVar_Day != 0 {
|
||||||
|
t, err := time.Parse("2006-01-02", input)
|
||||||
|
if err == nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowedFormats&TimeVar_Month != 0 {
|
||||||
|
t, err := time.Parse("2006-01", input)
|
||||||
|
if err == nil {
|
||||||
|
return t, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowedFormats&TimeVar_EpochSecond != 0 {
|
||||||
|
i, err := strconv.ParseInt(input, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
// If a customer enters 20200101 we don't want
|
||||||
|
// to parse that as an epoch time.
|
||||||
|
// This arbitrarily-chosen cutoff is around year 2000.
|
||||||
|
if i > 946000000 {
|
||||||
|
return time.Unix(i, 0), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, errors.New("Could not parse as absolute time.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagSet) TimeVar(i *TimeVar) {
|
||||||
|
initial := i.Default
|
||||||
|
if v, exist := os.LookupEnv(i.EnvVar); exist {
|
||||||
|
if d, err := parseTimeAlternatives(v, i.Formats); err == nil {
|
||||||
|
initial = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def := ""
|
||||||
|
if !i.Default.IsZero() {
|
||||||
|
def = i.Default.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
f.VarFlag(&VarFlag{
|
||||||
|
Name: i.Name,
|
||||||
|
Aliases: i.Aliases,
|
||||||
|
Usage: i.Usage,
|
||||||
|
Default: def,
|
||||||
|
EnvVar: i.EnvVar,
|
||||||
|
Value: newTimeValue(initial, i.Target, i.Hidden, i.Formats),
|
||||||
|
Completion: i.Completion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeValue struct {
|
||||||
|
hidden bool
|
||||||
|
target *time.Time
|
||||||
|
formats TimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTimeValue(def time.Time, target *time.Time, hidden bool, f TimeFormat) *timeValue {
|
||||||
|
*target = def
|
||||||
|
return &timeValue{
|
||||||
|
hidden: hidden,
|
||||||
|
target: target,
|
||||||
|
formats: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *timeValue) Set(s string) error {
|
||||||
|
v, err := parseTimeAlternatives(s, d.formats)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*d.target = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *timeValue) Get() interface{} { return *d.target }
|
||||||
|
func (d *timeValue) String() string { return (*d.target).String() }
|
||||||
|
func (d *timeValue) Example() string { return "time" }
|
||||||
|
func (d *timeValue) Hidden() bool { return d.hidden }
|
||||||
|
|
||||||
// -- helpers
|
// -- helpers
|
||||||
func envDefault(key, def string) string {
|
func envDefault(key, def string) string {
|
||||||
if v, exist := os.LookupEnv(key); exist {
|
if v, exist := os.LookupEnv(key); exist {
|
||||||
|
|||||||
115
command/base_flags_test.go
Normal file
115
command/base_flags_test.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_TimeParsing(t *testing.T) {
|
||||||
|
var zeroTime time.Time
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
Input string
|
||||||
|
Formats TimeFormat
|
||||||
|
Valid bool
|
||||||
|
Expected time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"2020-08-24",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
true,
|
||||||
|
time.Date(2020, 8, 24, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2099-09",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2099-09",
|
||||||
|
TimeVar_TimeOrDay | TimeVar_Month,
|
||||||
|
true,
|
||||||
|
time.Date(2099, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-01-02T03:04:05-02:00",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
true,
|
||||||
|
time.Date(2021, 1, 2, 5, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-01-02T03:04:05",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false, // Missing timezone not supported
|
||||||
|
time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-01-02T03:04:05+02:00",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
true,
|
||||||
|
time.Date(2021, 1, 2, 1, 4, 5, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1598313593",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
true,
|
||||||
|
time.Date(2020, 8, 24, 23, 59, 53, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2037",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"20201212",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"9999999999999999999999999999999999999999999999",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-13-02T03:04:05-02:00",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-12-02T24:04:05+00:00",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
false,
|
||||||
|
zeroTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2021-01-02T03:04:05.234567890Z",
|
||||||
|
TimeVar_TimeOrDay,
|
||||||
|
true,
|
||||||
|
time.Date(2021, 1, 2, 3, 4, 5, 234567890, time.UTC),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
var result time.Time
|
||||||
|
timeVal := newTimeValue(zeroTime, &result, false, tc.Formats)
|
||||||
|
err := timeVal.Set(tc.Input)
|
||||||
|
if err == nil && !tc.Valid {
|
||||||
|
t.Errorf("Time %q parsed without error as %v, but is not valid", tc.Input, result)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if tc.Valid {
|
||||||
|
t.Errorf("Time %q parsed as error, but is valid", tc.Input)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !tc.Expected.Equal(result) {
|
||||||
|
t.Errorf("Time %q parsed incorrectly, expected %v but got %v", tc.Input, tc.Expected.UTC(), result.UTC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user