Skip to content

Commit

Permalink
Improve time functions
Browse files Browse the repository at this point in the history
This PR changes name of the toTime template function to formatTime and
adds toTime and mustToTime, which parse a string and convert it to a time,
and toLayout that converts strings like time.RFC3339 to the actual
layout string.
  • Loading branch information
maraino committed Jul 2, 2024
1 parent c11e706 commit 5411da7
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 10 deletions.
96 changes: 90 additions & 6 deletions internal/templates/funcmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package templates

import (
"errors"
"strings"
"text/template"
"time"

Expand All @@ -10,12 +11,16 @@ import (
)

// GetFuncMap returns the list of functions provided by sprig. It adds the
// function "toTime" and changes the function "fail".
// function "formatTime", "toTime", "mustToTime", and changes the function
// "fail".
//
// The "toTime" function receives a time or a Unix epoch and formats it to
// RFC3339 in UTC. The "fail" function sets the provided message, so that
// template errors are reported directly to the template without having the
// wrapper that text/template adds.
// The "formatTime" function receives a time or a Unix epoch and formats it to
// RFC3339 in UTC. The "toTime" and "mustToTime" functions parses a formatted
// string and returns the time value it represents. The "toLayout" function
// converts strings like "time.RFC3339" or "time.UnixDate" to the actual layout
// represented by the Go constant with the same name. The "fail" function sets
// the provided message, so that template errors are reported directly to the
// template without having the wrapper that text/template adds.
//
// sprig "env" and "expandenv" functions are removed to avoid the leak of
// information.
Expand All @@ -27,11 +32,14 @@ func GetFuncMap(failMessage *string) template.FuncMap {
*failMessage = msg
return "", errors.New(msg)
}
m["formatTime"] = formatTime
m["toTime"] = toTime
m["mustToTime"] = mustToTime
m["toLayout"] = toLayout
return m
}

func toTime(v any) string {
func formatTime(v any) string {
var t time.Time
switch date := v.(type) {
case time.Time:
Expand All @@ -55,3 +63,79 @@ func toTime(v any) string {
}
return t.UTC().Format(time.RFC3339)
}

func toTime(v ...string) time.Time {
t, _ := mustToTime(v...)
return t

Check warning on line 69 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L67-L69

Added lines #L67 - L69 were not covered by tests
}

func mustToTime(v ...string) (time.Time, error) {
switch len(v) {
case 0:
return time.Now().UTC(), nil
case 1:
return time.Parse(time.RFC3339, v[0])
case 2:
layout := toLayout(v[0])
return time.Parse(layout, v[1])
case 3:
layout := toLayout(v[0])
loc, err := time.LoadLocation(v[2])
if err != nil {
return time.Time{}, err

Check warning on line 85 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L72-L85

Added lines #L72 - L85 were not covered by tests
}
return time.ParseInLocation(layout, v[1], loc)
default:
return time.Time{}, errors.New("unsupported number of parameters")

Check warning on line 89 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L87-L89

Added lines #L87 - L89 were not covered by tests
}
}

func toLayout(fmt string) string {
if !strings.HasPrefix(fmt, "time.") {
return fmt

Check warning on line 95 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L93-L95

Added lines #L93 - L95 were not covered by tests
}

switch fmt {
case "time.Layout":
return time.Layout
case "time.ANSIC":
return time.ANSIC
case "time.UnixDate":
return time.UnixDate
case "time.RubyDate":
return time.RubyDate
case "time.RFC822":
return time.RFC822
case "time.RFC822Z":
return time.RFC822Z
case "time.RFC850":
return time.RFC850
case "time.RFC1123":
return time.RFC1123
case "time.RFC1123Z":
return time.RFC1123Z
case "time.RFC3339":
return time.RFC3339
case "time.RFC3339Nano":
return time.RFC3339Nano

Check warning on line 120 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L98-L120

Added lines #L98 - L120 were not covered by tests
// From the ones bellow, only time.DateTime will parse a complete date.
case "time.Kitchen":
return time.Kitchen
case "time.Stamp":
return time.Stamp
case "time.StampMilli":
return time.StampMilli
case "time.StampMicro":
return time.StampMicro
case "time.StampNano":
return time.StampNano
case "time.DateTime":
return time.DateTime
case "time.DateOnly":
return time.DateOnly
case "time.TimeOnly":
return time.TimeOnly
default:
return fmt

Check warning on line 139 in internal/templates/funcmap.go

View check run for this annotation

Codecov / codecov/patch

internal/templates/funcmap.go#L122-L139

Added lines #L122 - L139 were not covered by tests
}
}
6 changes: 3 additions & 3 deletions internal/templates/funcmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func Test_GetFuncMap_fail(t *testing.T) {
}
}

func TestGetFuncMap_toTime(t *testing.T) {
func TestGetFuncMap_formatTime(t *testing.T) {
now := time.Now()
numericDate := jose.NewNumericDate(now)
expected := now.UTC().Format(time.RFC3339)
Expand Down Expand Up @@ -57,15 +57,15 @@ func TestGetFuncMap_toTime(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var failMesage string
fns := GetFuncMap(&failMesage)
fn := fns["toTime"].(func(any) string)
fn := fns["formatTime"].(func(any) string)
assert.Equal(t, tt.want, fn(tt.args.v))
})
}

t.Run("default", func(t *testing.T) {
var failMesage string
fns := GetFuncMap(&failMesage)
fn := fns["toTime"].(func(any) string)
fn := fns["formatTime"].(func(any) string)
want := time.Now()
got, err := time.Parse(time.RFC3339, fn(nil))
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion x509util/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func TestNewCertificateTemplate(t *testing.T) {
(dict "type" "userPrincipalName" "value" .Token.upn)
(dict "type" "1.2.3.4" "value" (printf "int:%s" .Insecure.User.id))
) | toJson }},
"notBefore": "{{ .Token.nbf | toTime }}",
"notBefore": "{{ .Token.nbf | formatTime }}",
"notAfter": {{ now | dateModify "24h" | toJson }},
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
Expand Down

0 comments on commit 5411da7

Please sign in to comment.