diff --git a/configtype/time.go b/configtype/time.go index e407dd8efe..9b22a9a8dd 100644 --- a/configtype/time.go +++ b/configtype/time.go @@ -15,9 +15,10 @@ import ( // when a time type is required. We wrap the time.Time type so that // the spec can be extended in the future to support other types of times type Time struct { - input string - time time.Time - duration *timeDuration + input string + time time.Time + duration *timeDuration + hashNowFunc func() time.Time } func ParseTime(s string) (Time, error) { @@ -130,6 +131,19 @@ func (t Time) String() string { return t.input } +func (t Time) Hash() (uint64, error) { + nowFunc := t.hashNowFunc + if nowFunc == nil { + nowFunc = time.Now + } + at := t.AsTime(nowFunc()) + return uint64(at.UnixNano()), nil +} + +func (t *Time) SetHashNowFunc(f func() time.Time) { + t.hashNowFunc = f +} + type timeDuration struct { input string diff --git a/configtype/time_test.go b/configtype/time_test.go index 245b350d30..c033badb8f 100644 --- a/configtype/time_test.go +++ b/configtype/time_test.go @@ -9,6 +9,7 @@ import ( "github.com/cloudquery/plugin-sdk/v4/configtype" "github.com/cloudquery/plugin-sdk/v4/plugin" "github.com/invopop/jsonschema" + "github.com/mitchellh/hashstructure/v2" "github.com/stretchr/testify/require" ) @@ -151,3 +152,63 @@ func TestTime_JSONSchema(t *testing.T) { }) } } + +func TestTime_Hashing(t *testing.T) { + hnf := func() time.Time { return time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) } + cases := []struct { + a configtype.Time + b configtype.Time + hashNowFunc func() time.Time + equal bool + }{ + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("10m"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("10m"); return ct }(), + hashNowFunc: hnf, + equal: true, + }, + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("10m"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("10m"); return ct }(), + equal: false, + }, + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("10m"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("1m"); return ct }(), + equal: false, + }, + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("2021-09-01T00:00:00Z"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("2012-01-02T01:02:03Z"); return ct }(), + hashNowFunc: hnf, + equal: false, + }, + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("-50m30s"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("50 minutes 30 seconds ago"); return ct }(), + hashNowFunc: hnf, + equal: true, + }, + { + a: func() configtype.Time { ct, _ := configtype.ParseTime("50m30s"); return ct }(), + b: func() configtype.Time { ct, _ := configtype.ParseTime("50 minutes 30 seconds from now"); return ct }(), + hashNowFunc: hnf, + equal: true, + }, + } + for _, tc := range cases { + tc.a.SetHashNowFunc(tc.hashNowFunc) + hashA, err := hashstructure.Hash(tc.a, hashstructure.FormatV2, nil) + require.NoError(t, err) + time.Sleep(1 * time.Second) + tc.b.SetHashNowFunc(tc.hashNowFunc) + hashB, err := hashstructure.Hash(tc.b, hashstructure.FormatV2, nil) + require.NoError(t, err) + + if tc.equal { + require.Equal(t, hashA, hashB) + continue + } + require.NotEqual(t, hashA, hashB) + } +} diff --git a/go.mod b/go.mod index ac97e350b9..b3b72e8068 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/invopop/jsonschema v0.13.0 + github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.47.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 diff --git a/go.sum b/go.sum index a1dd86c428..c22a044385 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,8 @@ github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpsp github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=