Merge pull request #6679 from thomastaylor312/feat/timestamp_hell

feat(*): Adds custom time package for better marshalling
pull/6691/head
Taylor Thomas 6 years ago committed by GitHub
commit 5a23663c6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,7 +22,6 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -34,6 +33,7 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
"helm.sh/helm/v3/pkg/time"
) )
func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }

@ -30,6 +30,7 @@ import (
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v3/pkg/cli/output"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
helmtime "helm.sh/helm/v3/pkg/time"
) )
var historyHelp = ` var historyHelp = `
@ -77,7 +78,7 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
type releaseInfo struct { type releaseInfo struct {
Revision int `json:"revision"` Revision int `json:"revision"`
Updated time.Time `json:"updated"` Updated helmtime.Time `json:"updated"`
Status string `json:"status"` Status string `json:"status"`
Chart string `json:"chart"` Chart string `json:"chart"`
AppVersion string `json:"app_version"` AppVersion string `json:"app_version"`

@ -18,10 +18,10 @@ package main
import ( import (
"testing" "testing"
"time"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/time"
) )
func TestListCmd(t *testing.T) { func TestListCmd(t *testing.T) {

@ -22,11 +22,12 @@ import (
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
) )
func TestStatusCmd(t *testing.T) { func TestStatusCmd(t *testing.T) {
releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release { releasesMockWithStatus := func(info *release.Info, hooks ...*release.Hook) []*release.Release {
info.LastDeployed = time.Unix(1452902400, 0).UTC() info.LastDeployed = helmtime.Unix(1452902400, 0).UTC()
return []*release.Release{{ return []*release.Release{{
Name: "flummoxed-chickadee", Name: "flummoxed-chickadee",
Namespace: "default", Namespace: "default",
@ -103,7 +104,7 @@ func TestStatusCmd(t *testing.T) {
runTestCmd(t, tests) runTestCmd(t, tests)
} }
func mustParseTime(t string) time.Time { func mustParseTime(t string) helmtime.Time {
res, _ := time.Parse(time.RFC3339, t) res, _ := helmtime.Parse(time.RFC3339, t)
return res return res
} }

@ -1 +1 @@
{"name":"flummoxed-chickadee","info":{"first_deployed":"0001-01-01T00:00:00Z","last_deployed":"2016-01-16T00:00:00Z","deleted":"0001-01-01T00:00:00Z","status":"deployed","notes":"release notes"},"namespace":"default"} {"name":"flummoxed-chickadee","info":{"first_deployed":"","last_deployed":"2016-01-16T00:00:00Z","deleted":"","status":"deployed","notes":"release notes"},"namespace":"default"}

@ -19,7 +19,6 @@ package action
import ( import (
"path" "path"
"regexp" "regexp"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -34,12 +33,13 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
"helm.sh/helm/v3/pkg/time"
) )
// Timestamper is a function capable of producing a timestamp.Timestamper. // Timestamper is a function capable of producing a timestamp.Timestamper.
// //
// By default, this is a time.Time function. This can be overridden for testing, // By default, this is a time.Time function from the Helm time package. This can
// though, so that timestamps are predictable. // be overridden for testing though, so that timestamps are predictable.
var Timestamper = time.Now var Timestamper = time.Now
var ( var (

@ -21,7 +21,6 @@ import (
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
dockerauth "github.com/deislabs/oras/pkg/auth/docker" dockerauth "github.com/deislabs/oras/pkg/auth/docker"
fakeclientset "k8s.io/client-go/kubernetes/fake" fakeclientset "k8s.io/client-go/kubernetes/fake"
@ -33,6 +32,7 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/storage/driver"
"helm.sh/helm/v3/pkg/time"
) )
var verbose = flag.Bool("test.log", false, "enable test logging") var verbose = flag.Bool("test.log", false, "enable test logging")

@ -23,6 +23,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
) )
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
@ -60,7 +61,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// Record the time at which the hook was applied to the cluster // Record the time at which the hook was applied to the cluster
h.LastRun = release.HookExecution{ h.LastRun = release.HookExecution{
StartedAt: time.Now(), StartedAt: helmtime.Now(),
Phase: release.HookPhaseRunning, Phase: release.HookPhaseRunning,
} }
cfg.recordRelease(rl) cfg.recordRelease(rl)
@ -72,7 +73,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// Create hook resources // Create hook resources
if _, err := cfg.KubeClient.Create(resources); err != nil { if _, err := cfg.KubeClient.Create(resources); err != nil {
h.LastRun.CompletedAt = time.Now() h.LastRun.CompletedAt = helmtime.Now()
h.LastRun.Phase = release.HookPhaseFailed h.LastRun.Phase = release.HookPhaseFailed
return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path)
} }
@ -80,7 +81,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
// Watch hook resources until they have completed // Watch hook resources until they have completed
err = cfg.KubeClient.WatchUntilReady(resources, timeout) err = cfg.KubeClient.WatchUntilReady(resources, timeout)
// Note the time of success/failure // Note the time of success/failure
h.LastRun.CompletedAt = time.Now() h.LastRun.CompletedAt = helmtime.Now()
// Mark hook as succeeded or failed // Mark hook as succeeded or failed
if err != nil { if err != nil {
h.LastRun.Phase = release.HookPhaseFailed h.LastRun.Phase = release.HookPhaseFailed

@ -25,6 +25,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
) )
// Rollback is the action for rolling back to a given release. // Rollback is the action for rolling back to a given release.
@ -119,7 +120,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
Config: previousRelease.Config, Config: previousRelease.Config,
Info: &release.Info{ Info: &release.Info{
FirstDeployed: currentRelease.Info.FirstDeployed, FirstDeployed: currentRelease.Info.FirstDeployed,
LastDeployed: time.Now(), LastDeployed: helmtime.Now(),
Status: release.StatusPendingRollback, Status: release.StatusPendingRollback,
Notes: previousRelease.Info.Notes, Notes: previousRelease.Info.Notes,
// Because we lose the reference to previous version elsewhere, we set the // Because we lose the reference to previous version elsewhere, we set the

@ -24,6 +24,7 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/releaseutil"
helmtime "helm.sh/helm/v3/pkg/time"
) )
// Uninstall is the action for uninstalling releases. // Uninstall is the action for uninstalling releases.
@ -89,7 +90,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
u.cfg.Log("uninstall: Deleting %s", name) u.cfg.Log("uninstall: Deleting %s", name)
rel.Info.Status = release.StatusUninstalling rel.Info.Status = release.StatusUninstalling
rel.Info.Deleted = time.Now() rel.Info.Deleted = helmtime.Now()
rel.Info.Description = "Deletion in progress (or silently failed)" rel.Info.Description = "Deletion in progress (or silently failed)"
res := &release.UninstallReleaseResponse{Release: rel} res := &release.UninstallReleaseResponse{Release: rel}

@ -19,7 +19,6 @@ package action
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
@ -28,6 +27,7 @@ import (
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/time"
) )
func upgradeAction(t *testing.T) *Upgrade { func upgradeAction(t *testing.T) *Upgrade {

@ -17,7 +17,7 @@ limitations under the License.
package release package release
import ( import (
"time" "helm.sh/helm/v3/pkg/time"
) )
// HookEvent specifies the hook event // HookEvent specifies the hook event

@ -15,7 +15,9 @@ limitations under the License.
package release package release
import "time" import (
"helm.sh/helm/v3/pkg/time"
)
// Info describes release information. // Info describes release information.
type Info struct { type Info struct {
@ -24,9 +26,9 @@ type Info struct {
// LastDeployed is when the release was last deployed. // LastDeployed is when the release was last deployed.
LastDeployed time.Time `json:"last_deployed,omitempty"` LastDeployed time.Time `json:"last_deployed,omitempty"`
// Deleted tracks when this object was deleted. // Deleted tracks when this object was deleted.
Deleted time.Time `json:"deleted,omitempty"` Deleted time.Time `json:"deleted"`
// Description is human-friendly "log entry" about this release. // Description is human-friendly "log entry" about this release.
Description string `json:"Description,omitempty"` Description string `json:"description,omitempty"`
// Status is the current state of the release // Status is the current state of the release
Status Status `json:"status,omitempty"` Status Status `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available // Contains the rendered templates/NOTES.txt if available

@ -18,9 +18,9 @@ package release
import ( import (
"math/rand" "math/rand"
"time"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/time"
) )
// MockHookTemplate is the hook template used for all mock release objects. // MockHookTemplate is the hook template used for all mock release objects.

@ -21,6 +21,7 @@ import (
"time" "time"
rspb "helm.sh/helm/v3/pkg/release" rspb "helm.sh/helm/v3/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time"
) )
// note: this test data is shared with filter_test.go. // note: this test data is shared with filter_test.go.
@ -33,8 +34,7 @@ var releases = []*rspb.Release{
} }
func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rspb.Release { func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rspb.Release {
tmsp := time.Now().Add(dur) info := &rspb.Info{Status: status, LastDeployed: helmtime.Now().Add(dur)}
info := &rspb.Info{Status: status, LastDeployed: tmsp}
return &rspb.Release{ return &rspb.Release{
Name: name, Name: name,
Version: vers, Version: vers,

@ -0,0 +1,91 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package time contains a wrapper for time.Time in the standard library and
// associated methods. This package mainly exists to workaround an issue in Go
// where the serializer doesn't omit an empty value for time:
// https://github.com/golang/go/issues/11939. As such, this can be removed if a
// proposal is ever accepted for Go
package time
import (
"bytes"
"time"
)
// emptyString contains an empty JSON string value to be used as output
var emptyString = `""`
// Time is a convenience wrapper around stdlib time, but with different
// marshalling and unmarshaling for zero values
type Time struct {
time.Time
}
// Now returns the current time. It is a convenience wrapper around time.Now()
func Now() Time {
return Time{time.Now()}
}
func (t Time) MarshalJSON() ([]byte, error) {
if t.Time.IsZero() {
return []byte(emptyString), nil
}
return t.Time.MarshalJSON()
}
func (t *Time) UnmarshalJSON(b []byte) error {
if bytes.Equal(b, []byte("null")) {
return nil
}
// If it is empty, we don't have to set anything since time.Time is not a
// pointer and will be set to the zero value
if bytes.Equal([]byte(emptyString), b) {
return nil
}
return t.Time.UnmarshalJSON(b)
}
func Parse(layout, value string) (Time, error) {
t, err := time.Parse(layout, value)
return Time{Time: t}, err
}
func ParseInLocation(layout, value string, loc *time.Location) (Time, error) {
t, err := time.ParseInLocation(layout, value, loc)
return Time{Time: t}, err
}
func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time {
return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)}
}
func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} }
func (t Time) Add(d time.Duration) Time { return Time{Time: t.Time.Add(d)} }
func (t Time) AddDate(years int, months int, days int) Time {
return Time{Time: t.Time.AddDate(years, months, days)}
}
func (t Time) After(u Time) bool { return t.Time.After(u.Time) }
func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) }
func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) }
func (t Time) In(loc *time.Location) Time { return Time{Time: t.Time.In(loc)} }
func (t Time) Local() Time { return Time{Time: t.Time.Local()} }
func (t Time) Round(d time.Duration) Time { return Time{Time: t.Time.Round(d)} }
func (t Time) Sub(u Time) time.Duration { return t.Time.Sub(u.Time) }
func (t Time) Truncate(d time.Duration) Time { return Time{Time: t.Time.Truncate(d)} }
func (t Time) UTC() Time { return Time{Time: t.Time.UTC()} }

@ -0,0 +1,83 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package time
import (
"encoding/json"
"testing"
"time"
)
var (
testingTime, _ = Parse(time.RFC3339, "1977-09-02T22:04:05Z")
testingTimeString = `"1977-09-02T22:04:05Z"`
)
func TestNonZeroValueMarshal(t *testing.T) {
res, err := json.Marshal(testingTime)
if err != nil {
t.Fatal(err)
}
if testingTimeString != string(res) {
t.Errorf("expected a marshaled value of %s, got %s", testingTimeString, res)
}
}
func TestZeroValueMarshal(t *testing.T) {
res, err := json.Marshal(Time{})
if err != nil {
t.Fatal(err)
}
if string(res) != emptyString {
t.Errorf("expected zero value to marshal to empty string, got %s", res)
}
}
func TestNonZeroValueUnmarshal(t *testing.T) {
var myTime Time
err := json.Unmarshal([]byte(testingTimeString), &myTime)
if err != nil {
t.Fatal(err)
}
if !myTime.Equal(testingTime) {
t.Errorf("expected time to be equal to %v, got %v", testingTime, myTime)
}
}
func TestEmptyStringUnmarshal(t *testing.T) {
var myTime Time
err := json.Unmarshal([]byte(emptyString), &myTime)
if err != nil {
t.Fatal(err)
}
if !myTime.IsZero() {
t.Errorf("expected time to be equal to zero value, got %v", myTime)
}
}
func TestZeroValueUnmarshal(t *testing.T) {
// This test ensures that we can unmarshal any time value that was output
// with the current go default value of "0001-01-01T00:00:00Z"
var myTime Time
err := json.Unmarshal([]byte(`"0001-01-01T00:00:00Z"`), &myTime)
if err != nil {
t.Fatal(err)
}
if !myTime.IsZero() {
t.Errorf("expected time to be equal to zero value, got %v", myTime)
}
}
Loading…
Cancel
Save