mirror of
https://github.com/helm/helm.git
synced 2026-08-06 08:01:19 +00:00
Merge pull request #6679 from thomastaylor312/feat/timestamp_hell
feat(*): Adds custom time package for better marshalling
This commit is contained in:
@@ -22,7 +22,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
shellwords "github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +33,7 @@ import (
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
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/release"
|
||||
"helm.sh/helm/v3/pkg/releaseutil"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
var historyHelp = `
|
||||
@@ -76,12 +77,12 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
}
|
||||
|
||||
type releaseInfo struct {
|
||||
Revision int `json:"revision"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Status string `json:"status"`
|
||||
Chart string `json:"chart"`
|
||||
AppVersion string `json:"app_version"`
|
||||
Description string `json:"description"`
|
||||
Revision int `json:"revision"`
|
||||
Updated helmtime.Time `json:"updated"`
|
||||
Status string `json:"status"`
|
||||
Chart string `json:"chart"`
|
||||
AppVersion string `json:"app_version"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type releaseHistory []releaseInfo
|
||||
|
||||
@@ -18,10 +18,10 @@ package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
func TestListCmd(t *testing.T) {
|
||||
|
||||
@@ -22,11 +22,12 @@ import (
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
func TestStatusCmd(t *testing.T) {
|
||||
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{{
|
||||
Name: "flummoxed-chickadee",
|
||||
Namespace: "default",
|
||||
@@ -103,7 +104,7 @@ func TestStatusCmd(t *testing.T) {
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
func mustParseTime(t string) time.Time {
|
||||
res, _ := time.Parse(time.RFC3339, t)
|
||||
func mustParseTime(t string) helmtime.Time {
|
||||
res, _ := helmtime.Parse(time.RFC3339, t)
|
||||
return res
|
||||
}
|
||||
|
||||
2
cmd/helm/testdata/output/status.json
vendored
2
cmd/helm/testdata/output/status.json
vendored
@@ -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 (
|
||||
"path"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
@@ -34,12 +33,13 @@ import (
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// Timestamper is a function capable of producing a timestamp.Timestamper.
|
||||
//
|
||||
// By default, this is a time.Time function. This can be overridden for testing,
|
||||
// though, so that timestamps are predictable.
|
||||
// By default, this is a time.Time function from the Helm time package. This can
|
||||
// be overridden for testing though, so that timestamps are predictable.
|
||||
var Timestamper = time.Now
|
||||
|
||||
var (
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dockerauth "github.com/deislabs/oras/pkg/auth/docker"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
var verbose = flag.Bool("test.log", false, "enable test logging")
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// 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
|
||||
h.LastRun = release.HookExecution{
|
||||
StartedAt: time.Now(),
|
||||
StartedAt: helmtime.Now(),
|
||||
Phase: release.HookPhaseRunning,
|
||||
}
|
||||
cfg.recordRelease(rl)
|
||||
@@ -72,7 +73,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
|
||||
|
||||
// Create hook resources
|
||||
if _, err := cfg.KubeClient.Create(resources); err != nil {
|
||||
h.LastRun.CompletedAt = time.Now()
|
||||
h.LastRun.CompletedAt = helmtime.Now()
|
||||
h.LastRun.Phase = release.HookPhaseFailed
|
||||
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
|
||||
err = cfg.KubeClient.WatchUntilReady(resources, timeout)
|
||||
// Note the time of success/failure
|
||||
h.LastRun.CompletedAt = time.Now()
|
||||
h.LastRun.CompletedAt = helmtime.Now()
|
||||
// Mark hook as succeeded or failed
|
||||
if err != nil {
|
||||
h.LastRun.Phase = release.HookPhaseFailed
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// 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,
|
||||
Info: &release.Info{
|
||||
FirstDeployed: currentRelease.Info.FirstDeployed,
|
||||
LastDeployed: time.Now(),
|
||||
LastDeployed: helmtime.Now(),
|
||||
Status: release.StatusPendingRollback,
|
||||
Notes: previousRelease.Info.Notes,
|
||||
// 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/releaseutil"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// 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)
|
||||
rel.Info.Status = release.StatusUninstalling
|
||||
rel.Info.Deleted = time.Now()
|
||||
rel.Info.Deleted = helmtime.Now()
|
||||
rel.Info.Description = "Deletion in progress (or silently failed)"
|
||||
res := &release.UninstallReleaseResponse{Release: rel}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package action
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
|
||||
@@ -28,6 +27,7 @@ import (
|
||||
|
||||
kubefake "helm.sh/helm/v3/pkg/kube/fake"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
func upgradeAction(t *testing.T) *Upgrade {
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
package release
|
||||
|
||||
import (
|
||||
"time"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// HookEvent specifies the hook event
|
||||
|
||||
@@ -15,7 +15,9 @@ limitations under the License.
|
||||
|
||||
package release
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// Info describes release information.
|
||||
type Info struct {
|
||||
@@ -24,9 +26,9 @@ type Info struct {
|
||||
// LastDeployed is when the release was last deployed.
|
||||
LastDeployed time.Time `json:"last_deployed,omitempty"`
|
||||
// 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 string `json:"Description,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
// Status is the current state of the release
|
||||
Status Status `json:"status,omitempty"`
|
||||
// Contains the rendered templates/NOTES.txt if available
|
||||
|
||||
@@ -18,9 +18,9 @@ package release
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// MockHookTemplate is the hook template used for all mock release objects.
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
rspb "helm.sh/helm/v3/pkg/release"
|
||||
helmtime "helm.sh/helm/v3/pkg/time"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
tmsp := time.Now().Add(dur)
|
||||
info := &rspb.Info{Status: status, LastDeployed: tmsp}
|
||||
info := &rspb.Info{Status: status, LastDeployed: helmtime.Now().Add(dur)}
|
||||
return &rspb.Release{
|
||||
Name: name,
|
||||
Version: vers,
|
||||
|
||||
91
pkg/time/time.go
Normal file
91
pkg/time/time.go
Normal file
@@ -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()} }
|
||||
83
pkg/time/time_test.go
Normal file
83
pkg/time/time_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user