Merge pull request #59 from technosophos/feat/helm-get-status

feat(tiller): implement get and status
pull/613/head
Matt Butcher 9 years ago
commit 7142189c35

@ -5,7 +5,7 @@ package hapi.services.tiller;
import "hapi/chart/chart.proto"; import "hapi/chart/chart.proto";
import "hapi/chart/config.proto"; import "hapi/chart/config.proto";
import "hapi/release/release.proto"; import "hapi/release/release.proto";
import "hapi/release/status.proto"; import "hapi/release/info.proto";
option go_package = "services"; option go_package = "services";
@ -89,55 +89,39 @@ message ListReleasesRequest {
message ListReleasesResponse { message ListReleasesResponse {
// The expected total number of releases to be returned // The expected total number of releases to be returned
int64 count = 1; int64 count = 1;
// The zero-based offset at which the list is positioned // The zero-based offset at which the list is positioned
int64 offset = 2; int64 offset = 2;
// The total number of queryable releases // The total number of queryable releases
int64 total = 3; int64 total = 3;
// The resulting releases // The resulting releases
repeated hapi.release.Release releases = 4; repeated hapi.release.Release releases = 4;
} }
// // GetReleaseStatusRequest is a request to get the status of a release.
// GetReleaseStatusRequest:
//
// TODO
//
message GetReleaseStatusRequest { message GetReleaseStatusRequest {
// The name of the release // Name is the name of the release
string release_name = 1; string name = 1;
} }
// // GetReleaseStatusResponse is the response indicating the status of the named release.
// GetReleaseStatusResponse:
//
// TODO
//
message GetReleaseStatusResponse { message GetReleaseStatusResponse {
// The name of the release // Name is the name of the release.
string release_name = 1; string name = 1;
// The release status // Info contains information about the release.
hapi.release.Status release_status = 2; hapi.release.Info info = 2;
} }
// // GetReleaseContentRequest is a request to get the contents of a release.
// GetReleaseContentRequest:
//
// TODO
//
message GetReleaseContentRequest { message GetReleaseContentRequest {
// The name of the release // The name of the release
string release_name = 1; string name = 1;
} }
// // GetReleaseContentResponse is a response containing the contents of a release.
// GetReleaseContentResponse:
//
// TODO
//
message GetReleaseContentResponse { message GetReleaseContentResponse {
// The release content // The release content
hapi.release.Release release = 1; hapi.release.Release release = 1;

@ -0,0 +1,53 @@
package main
import (
"errors"
"fmt"
"github.com/deis/tiller/pkg/helm"
"github.com/spf13/cobra"
)
var getHelp = `
This command shows the details of a named release.
It can be used to get extended information about the release, including:
- The values used to generate the release
- The chart used to generate the release
- The generated manifest file
By default, this prints a human readable collection of information about the
chart, the supplied values, and the generated manifest file.
`
var errReleaseRequired = errors.New("release name is required")
var getCommand = &cobra.Command{
Use: "get [flags] RELEASE_NAME",
Short: "Download a named release",
Long: getHelp,
RunE: getCmd,
}
func init() {
RootCommand.AddCommand(getCommand)
}
func getCmd(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
}
res, err := helm.GetReleaseContent(args[0])
if err != nil {
return err
}
fmt.Printf("Chart/Version: %s %s\n", res.Release.Chart.Metadata.Name, res.Release.Chart.Metadata.Version)
fmt.Println("Config:")
fmt.Println(res.Release.Config)
fmt.Println("\nManifest:")
fmt.Println(res.Release.Manifest)
return nil
}

@ -0,0 +1,44 @@
package main
import (
"fmt"
"time"
"github.com/deis/tiller/pkg/helm"
"github.com/deis/tiller/pkg/timeconv"
"github.com/spf13/cobra"
)
var statusHelp = `
This command shows the status of a named release.
`
var statusCommand = &cobra.Command{
Use: "status [flags] RELEASE_NAME",
Short: "Displays the status of the named release",
Long: statusHelp,
RunE: status,
}
func init() {
RootCommand.AddCommand(statusCommand)
}
func status(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errReleaseRequired
}
res, err := helm.GetReleaseStatus(args[0])
if err != nil {
return err
}
fmt.Printf("Last Deployed: %s\n", timeconv.Format(res.Info.LastDeployed, time.ANSIC))
fmt.Printf("Status: %s\n", res.Info.Status.Code)
if res.Info.Status.Details != nil {
fmt.Printf("Details: %s\n", res.Info.Status.Details)
}
return nil
}

@ -4,12 +4,11 @@ import (
"bytes" "bytes"
"errors" "errors"
"log" "log"
"time"
"github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/cmd/tiller/environment"
"github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/release"
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
"github.com/golang/protobuf/ptypes/timestamp" "github.com/deis/tiller/pkg/timeconv"
"github.com/technosophos/moniker" "github.com/technosophos/moniker"
ctx "golang.org/x/net/context" ctx "golang.org/x/net/context"
) )
@ -39,11 +38,25 @@ func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream s
} }
func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) {
return nil, errNotImplemented if req.Name == "" {
return nil, errMissingRelease
}
rel, err := s.env.Releases.Read(req.Name)
if err != nil {
return nil, err
}
if rel.Info == nil {
return nil, errors.New("release info is missing")
}
return &services.GetReleaseStatusResponse{Info: rel.Info}, nil
} }
func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) {
return nil, errNotImplemented if req.Name == "" {
return nil, errMissingRelease
}
rel, err := s.env.Releases.Read(req.Name)
return &services.GetReleaseContentResponse{Release: rel}, err
} }
func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) {
@ -59,7 +72,7 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
namer := moniker.New() namer := moniker.New()
// TODO: Make sure this is unique. // TODO: Make sure this is unique.
name := namer.NameSep("-") name := namer.NameSep("-")
ts := now() ts := timeconv.Now()
// Render the templates // Render the templates
files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values)
@ -102,15 +115,6 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea
return &services.InstallReleaseResponse{Release: r}, nil return &services.InstallReleaseResponse{Release: r}, nil
} }
func now() *timestamp.Timestamp {
t := time.Now()
ts := &timestamp.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
return ts
}
func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) {
if req.Name == "" { if req.Name == "" {
log.Printf("uninstall: Release not found: %s", req.Name) log.Printf("uninstall: Release not found: %s", req.Name)
@ -125,7 +129,7 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR
log.Printf("uninstall: Deleting %s", req.Name) log.Printf("uninstall: Deleting %s", req.Name)
rel.Info.Status.Code = release.Status_DELETED rel.Info.Status.Code = release.Status_DELETED
rel.Info.Deleted = now() rel.Info.Deleted = timeconv.Now()
// TODO: Once KubeClient is ready, delete the resources. // TODO: Once KubeClient is ready, delete the resources.
log.Println("WARNING: Currently not deleting resources from k8s") log.Println("WARNING: Currently not deleting resources from k8s")

@ -9,6 +9,8 @@ import (
"github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/release"
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
"github.com/deis/tiller/pkg/storage" "github.com/deis/tiller/pkg/storage"
"github.com/deis/tiller/pkg/timeconv"
"github.com/golang/protobuf/ptypes/timestamp"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -18,6 +20,28 @@ func rsFixture() *releaseServer {
} }
} }
func releaseMock() *release.Release {
date := timestamp.Timestamp{242085845, 0}
return &release.Release{
Name: "angry-panda",
Info: &release.Info{
FirstDeployed: &date,
LastDeployed: &date,
Status: &release.Status{Code: release.Status_DEPLOYED},
},
Chart: &chart.Chart{
Metadata: &chart.Metadata{
Name: "foo",
Version: "0.1.0-beta.1",
},
Templates: []*chart.Template{
{Name: "foo.tpl", Data: []byte("Hello")},
},
},
Config: &chart.Config{Raw: `name = "value"`},
}
}
func TestInstallRelease(t *testing.T) { func TestInstallRelease(t *testing.T) {
c := context.Background() c := context.Background()
rs := rsFixture() rs := rsFixture()
@ -104,7 +128,7 @@ func TestUninstallRelease(t *testing.T) {
rs.env.Releases.Create(&release.Release{ rs.env.Releases.Create(&release.Release{
Name: "angry-panda", Name: "angry-panda",
Info: &release.Info{ Info: &release.Info{
FirstDeployed: now(), FirstDeployed: timeconv.Now(),
Status: &release.Status{ Status: &release.Status{
Code: release.Status_DEPLOYED, Code: release.Status_DEPLOYED,
}, },
@ -133,6 +157,42 @@ func TestUninstallRelease(t *testing.T) {
} }
} }
func TestGetReleaseContent(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseMock()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name {
t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name)
}
}
func TestGetReleaseStatus(t *testing.T) {
c := context.Background()
rs := rsFixture()
rel := releaseMock()
if err := rs.env.Releases.Create(rel); err != nil {
t.Fatalf("Could not store mock release: %s", err)
}
res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name})
if err != nil {
t.Errorf("Error getting release content: %s", err)
}
if res.Info.Status.Code != release.Status_DEPLOYED {
t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code)
}
}
func mockEnvironment() *environment.Environment { func mockEnvironment() *environment.Environment {
e := environment.New() e := environment.New()
e.Releases = storage.NewMemory() e.Releases = storage.NewMemory()

@ -4,6 +4,7 @@ import (
"github.com/deis/tiller/pkg/chart" "github.com/deis/tiller/pkg/chart"
chartpb "github.com/deis/tiller/pkg/proto/hapi/chart" chartpb "github.com/deis/tiller/pkg/proto/hapi/chart"
"github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/proto/hapi/services"
"golang.org/x/net/context"
) )
// Config defines a gRPC client's configuration. // Config defines a gRPC client's configuration.
@ -19,12 +20,26 @@ func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, err
// GetReleaseStatus returns the given release's status. // GetReleaseStatus returns the given release's status.
func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) { func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) {
return nil, errNotImplemented c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.GetReleaseStatusRequest{Name: name}
return c.impl.GetReleaseStatus(context.TODO(), req, c.cfg.CallOpts()...)
} }
// GetReleaseContent returns the configuration for a given release. // GetReleaseContent returns the configuration for a given release.
func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) { func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) {
return nil, errNotImplemented c := Config.client()
if err := c.dial(); err != nil {
return nil, err
}
defer c.Close()
req := &services.GetReleaseContentRequest{Name: name}
return c.impl.GetReleaseContent(context.TODO(), req, c.cfg.CallOpts()...)
} }
// UpdateRelease updates a release to a new/different chart. // UpdateRelease updates a release to a new/different chart.

@ -10,7 +10,7 @@ import math "math"
import hapi_chart3 "github.com/deis/tiller/pkg/proto/hapi/chart" import hapi_chart3 "github.com/deis/tiller/pkg/proto/hapi/chart"
import hapi_chart "github.com/deis/tiller/pkg/proto/hapi/chart" import hapi_chart "github.com/deis/tiller/pkg/proto/hapi/chart"
import hapi_release2 "github.com/deis/tiller/pkg/proto/hapi/release" import hapi_release2 "github.com/deis/tiller/pkg/proto/hapi/release"
import hapi_release "github.com/deis/tiller/pkg/proto/hapi/release" import hapi_release1 "github.com/deis/tiller/pkg/proto/hapi/release"
import ( import (
context "golang.org/x/net/context" context "golang.org/x/net/context"
@ -67,14 +67,10 @@ func (m *ListReleasesResponse) GetReleases() []*hapi_release2.Release {
return nil return nil
} }
// // GetReleaseStatusRequest is a request to get the status of a release.
// GetReleaseStatusRequest:
//
// TODO
//
type GetReleaseStatusRequest struct { type GetReleaseStatusRequest struct {
// The name of the release // Name is the name of the release
ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
} }
func (m *GetReleaseStatusRequest) Reset() { *m = GetReleaseStatusRequest{} } func (m *GetReleaseStatusRequest) Reset() { *m = GetReleaseStatusRequest{} }
@ -82,16 +78,12 @@ func (m *GetReleaseStatusRequest) String() string { return proto.Comp
func (*GetReleaseStatusRequest) ProtoMessage() {} func (*GetReleaseStatusRequest) ProtoMessage() {}
func (*GetReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} } func (*GetReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} }
// // GetReleaseStatusResponse is the response indicating the status of the named release.
// GetReleaseStatusResponse:
//
// TODO
//
type GetReleaseStatusResponse struct { type GetReleaseStatusResponse struct {
// The name of the release // Name is the name of the release.
ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// The release status // Info contains information about the release.
ReleaseStatus *hapi_release.Status `protobuf:"bytes,2,opt,name=release_status,json=releaseStatus" json:"release_status,omitempty"` Info *hapi_release1.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"`
} }
func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} } func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} }
@ -99,21 +91,17 @@ func (m *GetReleaseStatusResponse) String() string { return proto.Com
func (*GetReleaseStatusResponse) ProtoMessage() {} func (*GetReleaseStatusResponse) ProtoMessage() {}
func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} } func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} }
func (m *GetReleaseStatusResponse) GetReleaseStatus() *hapi_release.Status { func (m *GetReleaseStatusResponse) GetInfo() *hapi_release1.Info {
if m != nil { if m != nil {
return m.ReleaseStatus return m.Info
} }
return nil return nil
} }
// // GetReleaseContentRequest is a request to get the contents of a release.
// GetReleaseContentRequest:
//
// TODO
//
type GetReleaseContentRequest struct { type GetReleaseContentRequest struct {
// The name of the release // The name of the release
ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
} }
func (m *GetReleaseContentRequest) Reset() { *m = GetReleaseContentRequest{} } func (m *GetReleaseContentRequest) Reset() { *m = GetReleaseContentRequest{} }
@ -121,11 +109,7 @@ func (m *GetReleaseContentRequest) String() string { return proto.Com
func (*GetReleaseContentRequest) ProtoMessage() {} func (*GetReleaseContentRequest) ProtoMessage() {}
func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} } func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} }
// // GetReleaseContentResponse is a response containing the contents of a release.
// GetReleaseContentResponse:
//
// TODO
//
type GetReleaseContentResponse struct { type GetReleaseContentResponse struct {
// The release content // The release content
Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"`
@ -544,40 +528,39 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
} }
var fileDescriptor1 = []byte{ var fileDescriptor1 = []byte{
// 558 bytes of a gzipped FileDescriptorProto // 540 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x3d, 0x73, 0xd3, 0x40, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0x8d, 0x71, 0xe2, 0x98, 0x75, 0xe2, 0x21, 0x8b, 0x6c, 0x29, 0xaa, 0xc2, 0x35, 0x84, 0x40, 0x10, 0xae, 0x49, 0x9a, 0x86, 0x29, 0x54, 0x74, 0xc8, 0x8f, 0xf1, 0xa9, 0xda, 0x03, 0x94, 0x42,
0x64, 0x30, 0x25, 0xd0, 0xe0, 0x82, 0xc9, 0x90, 0xa1, 0x10, 0x93, 0x86, 0x26, 0x23, 0x9c, 0x33, 0x1d, 0x08, 0x6f, 0x40, 0x0e, 0x28, 0xa2, 0xa7, 0x45, 0xe5, 0xc0, 0x05, 0x99, 0x74, 0x43, 0x17,
0x11, 0x23, 0x9f, 0x8c, 0xee, 0xe4, 0x19, 0xe8, 0x29, 0xf9, 0x3f, 0xfc, 0x3c, 0xa4, 0xfb, 0xd0, 0x39, 0xeb, 0xe0, 0x5d, 0x47, 0xe2, 0x01, 0x38, 0xf2, 0x3e, 0x3c, 0x1e, 0xf6, 0xfe, 0x58, 0x71,
0x48, 0xb6, 0x34, 0x11, 0x69, 0x64, 0xdf, 0xed, 0xdb, 0x7d, 0xfb, 0xf1, 0x56, 0x02, 0xf7, 0x36, 0x62, 0x53, 0xab, 0x17, 0xb7, 0xbb, 0xdf, 0x37, 0xf3, 0xcd, 0xce, 0x7c, 0xa3, 0x40, 0x70, 0x1b,
0x58, 0x85, 0x13, 0x4e, 0x93, 0x75, 0x38, 0xa7, 0x7c, 0x22, 0xc2, 0x28, 0xa2, 0x89, 0xb7, 0x4a, 0xad, 0xf9, 0x44, 0xb2, 0x74, 0xc3, 0x17, 0x4c, 0x4e, 0x14, 0x8f, 0x63, 0x96, 0x86, 0xeb, 0x34,
0x62, 0x11, 0xa3, 0x95, 0xdb, 0x3c, 0x63, 0xf3, 0x94, 0xcd, 0x1d, 0x4b, 0x8f, 0xf9, 0x6d, 0x90, 0x51, 0x09, 0x0e, 0x0a, 0x2c, 0x74, 0x58, 0x68, 0xb0, 0x60, 0xa4, 0x23, 0x16, 0xb7, 0x51, 0xaa,
0x08, 0xf5, 0x54, 0x68, 0xd7, 0x2e, 0xdf, 0xc7, 0x6c, 0x11, 0x7e, 0xd3, 0x06, 0x45, 0x91, 0xd0, 0xcc, 0xd7, 0xb0, 0x83, 0xf1, 0xf6, 0x7d, 0x22, 0x96, 0xfc, 0xbb, 0x05, 0x8c, 0x44, 0xca, 0x62,
0x88, 0x06, 0x9c, 0x9a, 0x5f, 0x6d, 0x3b, 0xae, 0xd8, 0xb8, 0x08, 0x44, 0xca, 0x95, 0x89, 0xcc, 0x16, 0x49, 0xe6, 0xfe, 0x56, 0x82, 0x1c, 0xc6, 0xc5, 0x32, 0x31, 0x00, 0x99, 0xc1, 0xd3, 0x2b,
0xe0, 0xf1, 0x65, 0xc8, 0x85, 0xaf, 0x6c, 0xdc, 0xa7, 0x3f, 0x52, 0xca, 0x05, 0x5a, 0xb0, 0x17, 0x2e, 0x15, 0x35, 0x88, 0xa4, 0xec, 0x67, 0xc6, 0xa4, 0xc2, 0x01, 0x1c, 0xc6, 0x7c, 0xc5, 0x95,
0x85, 0xcb, 0x50, 0x38, 0x9d, 0x93, 0xce, 0x69, 0xd7, 0x57, 0x07, 0x1c, 0x43, 0x2f, 0x5e, 0x2c, 0xef, 0x9d, 0x79, 0xe7, 0x1d, 0x6a, 0x0e, 0x38, 0x82, 0x5e, 0xb2, 0x5c, 0x4a, 0xa6, 0xfc, 0x07,
0x38, 0x15, 0xce, 0x03, 0x79, 0xad, 0x4f, 0xe4, 0x4f, 0x07, 0xac, 0x6a, 0x14, 0xbe, 0x8a, 0x19, 0xfa, 0xda, 0x9e, 0xc8, 0x1f, 0x0f, 0x06, 0xd5, 0x2c, 0x72, 0x9d, 0x08, 0xc9, 0x8a, 0x34, 0x8b,
0xa7, 0x79, 0x98, 0x79, 0x9c, 0xb2, 0x22, 0x8c, 0x3c, 0x34, 0x85, 0xc9, 0xd1, 0x22, 0x16, 0x41, 0x24, 0x13, 0x65, 0x1a, 0x7d, 0x68, 0x4a, 0x53, 0xb0, 0x55, 0xa2, 0xa2, 0xd8, 0xef, 0x18, 0xb6,
0xe4, 0x74, 0x15, 0x5a, 0x1e, 0xf0, 0x15, 0xf4, 0x75, 0xe6, 0xdc, 0xd9, 0x3d, 0xe9, 0x9e, 0x0e, 0x3e, 0xe0, 0x5b, 0xe8, 0xdb, 0xba, 0xa5, 0xdf, 0x3d, 0xeb, 0x9c, 0x1f, 0x4f, 0x87, 0xa1, 0x6e,
0xa6, 0x23, 0x4f, 0xb6, 0xcc, 0xd4, 0xa8, 0x59, 0xfd, 0x02, 0x46, 0xde, 0x82, 0xfd, 0x81, 0x9a, 0x98, 0x7b, 0xa1, 0x55, 0xa5, 0x25, 0x8d, 0x5c, 0xc2, 0xf8, 0x03, 0x73, 0xd5, 0x7c, 0x52, 0x91,
0x6c, 0x3e, 0xcb, 0x72, 0x4d, 0x61, 0x4f, 0xe0, 0x40, 0xc3, 0xae, 0x59, 0xb0, 0xa4, 0x32, 0xb1, 0xca, 0xca, 0x87, 0x21, 0x74, 0x45, 0xb4, 0x62, 0xba, 0xa0, 0x87, 0x54, 0xff, 0x4f, 0x3e, 0x83,
0x87, 0xfe, 0x40, 0xdf, 0x7d, 0xca, 0xae, 0xc8, 0x2f, 0x70, 0xb6, 0xbd, 0x75, 0x41, 0x77, 0xbb, 0xbf, 0x4f, 0xb7, 0x2f, 0xa8, 0xe1, 0xe3, 0x73, 0xe8, 0x16, 0x1d, 0xd4, 0xd5, 0x1f, 0x4f, 0xb1,
0xe3, 0x1b, 0x18, 0x1a, 0x88, 0xea, 0xb4, 0xac, 0x72, 0x30, 0xb5, 0xaa, 0x59, 0xeb, 0xc0, 0x87, 0x5a, 0xcd, 0x3c, 0x47, 0xa8, 0xc6, 0x49, 0xb8, 0x9d, 0x77, 0x96, 0x08, 0xc5, 0x84, 0xfa, 0x5f,
0x49, 0x99, 0x87, 0xbc, 0x2b, 0x73, 0xcf, 0x62, 0x26, 0x28, 0x13, 0xff, 0x91, 0xfa, 0x25, 0x1c, 0x1d, 0x57, 0xf0, 0xac, 0x86, 0x6f, 0x0b, 0x99, 0xc0, 0x91, 0x95, 0xd0, 0x31, 0x8d, 0x5d, 0x70,
0xd7, 0xb8, 0xeb, 0xdc, 0x27, 0xb0, 0xaf, 0xb1, 0xd2, 0xb5, 0xb1, 0x8f, 0x06, 0x45, 0xc6, 0x60, 0x2c, 0x32, 0x82, 0xc1, 0xf5, 0xfa, 0x26, 0x52, 0xcc, 0x21, 0x46, 0x99, 0x8c, 0x61, 0xb8, 0x73,
0x5d, 0xad, 0x6e, 0x02, 0x41, 0x8d, 0x45, 0x25, 0x42, 0x6c, 0x18, 0x6d, 0xdc, 0x2b, 0x06, 0xf2, 0x6f, 0x14, 0xc8, 0x6f, 0x0f, 0x86, 0x73, 0x21, 0xf3, 0x9e, 0xc7, 0xd5, 0x10, 0x7c, 0x91, 0x8f,
0xbb, 0x03, 0xa3, 0x0b, 0x96, 0x55, 0x1d, 0x45, 0x55, 0x17, 0x7c, 0x9a, 0x09, 0x21, 0xd7, 0xac, 0xb1, 0xf0, 0x9b, 0x55, 0x3e, 0x35, 0xca, 0xc6, 0x94, 0xb3, 0xe2, 0x4b, 0x0d, 0x8e, 0x17, 0xd0,
0x66, 0x3e, 0x52, 0xcc, 0x4a, 0xd8, 0xb3, 0xfc, 0xe9, 0x2b, 0x3b, 0x9e, 0x41, 0x6f, 0x1d, 0x44, 0xdb, 0x44, 0x71, 0x1e, 0x53, 0xed, 0x8d, 0x65, 0x6a, 0xb3, 0x52, 0xcb, 0xc0, 0x31, 0x1c, 0xdd,
0x99, 0x8f, 0xee, 0x1a, 0x56, 0x90, 0x52, 0xf0, 0xbe, 0x46, 0xa0, 0x0d, 0xfb, 0x37, 0xc9, 0xcf, 0xa4, 0xbf, 0xbe, 0xa6, 0x99, 0xd0, 0xf3, 0xee, 0xd3, 0x5e, 0x7e, 0xa4, 0x99, 0x20, 0x73, 0x18,
0xeb, 0x24, 0x65, 0x52, 0x31, 0x7d, 0xbf, 0x97, 0x1d, 0xfd, 0x94, 0x91, 0x0b, 0x18, 0x6f, 0xa6, 0xed, 0x96, 0x71, 0xdf, 0x1e, 0xe4, 0x46, 0xb8, 0x16, 0xbc, 0xf6, 0x4d, 0x75, 0x03, 0xf8, 0x08,
0x71, 0xdf, 0x1e, 0x9c, 0x83, 0x7d, 0xc5, 0xc2, 0xda, 0x9a, 0x10, 0x76, 0x4b, 0x73, 0x90, 0xff, 0xfe, 0x3e, 0xfd, 0x9e, 0xda, 0xd3, 0xbf, 0x87, 0x70, 0xe2, 0x3c, 0x65, 0x56, 0x1b, 0x39, 0x3c,
0xc9, 0x47, 0x70, 0xb6, 0xe1, 0xf7, 0xe4, 0x9e, 0xfe, 0xdd, 0x83, 0xa1, 0x91, 0xa1, 0x7a, 0x3d, 0xda, 0x5e, 0x13, 0x7c, 0x19, 0xd6, 0x6d, 0x7e, 0x58, 0xb3, 0x90, 0xc1, 0x45, 0x1b, 0xaa, 0x1d,
0x60, 0x08, 0x07, 0xe5, 0x45, 0xc3, 0x67, 0x5e, 0xdd, 0xdb, 0xc3, 0xab, 0x59, 0x69, 0xf7, 0xac, 0xe4, 0xc1, 0x1b, 0x0f, 0x25, 0x3c, 0xd9, 0xf5, 0x34, 0x5e, 0xd6, 0xe7, 0x68, 0x58, 0x95, 0x20,
0x0d, 0x54, 0x0f, 0x72, 0xe7, 0x65, 0x07, 0x39, 0x3c, 0xda, 0x5c, 0x03, 0x3c, 0xaf, 0x8f, 0xd1, 0x6c, 0x4b, 0x77, 0xb2, 0xb8, 0x81, 0xd3, 0x3d, 0x03, 0xe3, 0x9d, 0x69, 0xaa, 0x9b, 0x11, 0x4c,
0xb0, 0x6c, 0xae, 0xd7, 0x16, 0x6e, 0x68, 0x71, 0x0d, 0x47, 0x5b, 0x02, 0xc6, 0x3b, 0xc3, 0x54, 0x5a, 0xf3, 0x4b, 0xdd, 0x1f, 0xf0, 0xb8, 0x62, 0x69, 0x6c, 0xe8, 0x56, 0xdd, 0x3e, 0x04, 0xaf,
0x17, 0xc5, 0x9d, 0xb4, 0xc6, 0x17, 0xbc, 0xdf, 0xe1, 0xb0, 0x22, 0x69, 0x6c, 0xe8, 0x56, 0xdd, 0x5a, 0x71, 0x4b, 0xad, 0x15, 0x9c, 0x54, 0xdd, 0x89, 0x0d, 0x09, 0x6a, 0x57, 0x29, 0x78, 0xdd,
0x3e, 0xb8, 0xcf, 0x5b, 0x61, 0x0b, 0xae, 0x25, 0x0c, 0xab, 0xea, 0xc4, 0x86, 0x00, 0xb5, 0xab, 0x8e, 0x5c, 0xca, 0xe5, 0x73, 0xdc, 0xb5, 0x64, 0xd3, 0x1c, 0x1b, 0x9c, 0xde, 0x34, 0xc7, 0x26,
0xe4, 0xbe, 0x68, 0x07, 0x2e, 0xe8, 0xb2, 0x39, 0x6e, 0x4a, 0xb2, 0x69, 0x8e, 0x0d, 0x4a, 0x6f, 0xa7, 0x93, 0x83, 0xf7, 0xf0, 0xa5, 0xef, 0xd8, 0xdf, 0x7a, 0xfa, 0x77, 0xe2, 0xdd, 0xbf, 0x00,
0x9a, 0x63, 0x93, 0xd2, 0xc9, 0xce, 0x7b, 0xf8, 0xd2, 0x37, 0xe8, 0xaf, 0x3d, 0xf9, 0xa5, 0x79, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xd6, 0xc7, 0x2c, 0xc1, 0x06, 0x00, 0x00,
0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x15, 0x68, 0xaf, 0x05, 0x07, 0x00, 0x00,
} }

@ -0,0 +1,7 @@
/*Package timeconv contains utilities for converting time.
The gRPC/Protobuf libraries contain time implementations that require conversion
to and from Go times. This library provides utilities and convenience functions
for performing conversions.
*/
package timeconv

@ -0,0 +1,32 @@
package timeconv
import (
"time"
"github.com/golang/protobuf/ptypes/timestamp"
)
// Now creates a timestamp.Timestamp representing the current time.
func Now() *timestamp.Timestamp {
return Timestamp(time.Now())
}
// Timestamp converts a time.Time to a protobuf *timestamp.Timestamp.
func Timestamp(t time.Time) *timestamp.Timestamp {
return &timestamp.Timestamp{
Seconds: t.Unix(),
Nanos: int32(t.Nanosecond()),
}
}
// Time converts a protobuf *timestamp.Timestamp to a time.Time.
func Time(ts *timestamp.Timestamp) time.Time {
return time.Unix(ts.Seconds, int64(ts.Nanos))
}
// Format formats a *timestamp.Timestamp into a string.
//
// This follows the rules for time.Time.Format().
func Format(ts *timestamp.Timestamp, layout string) string {
return Time(ts).Format(layout)
}

@ -0,0 +1,46 @@
package timeconv
import (
"testing"
"time"
)
func TestNow(t *testing.T) {
now := time.Now()
ts := Now()
var drift int64 = 5
if ts.Seconds < int64(now.Second())-drift {
t.Errorf("Unexpected time drift: %d", ts.Seconds)
}
}
func TestTimestamp(t *testing.T) {
now := time.Now()
ts := Timestamp(now)
if now.Unix() != ts.Seconds {
t.Errorf("Unexpected time drift: %d to %d", now.Second(), ts.Seconds)
}
if now.Nanosecond() != int(ts.Nanos) {
t.Errorf("Unexpected nano drift: %d to %d", now.Nanosecond(), ts.Nanos)
}
}
func TestTime(t *testing.T) {
nowts := Now()
now := Time(nowts)
if now.Unix() != nowts.Seconds {
t.Errorf("Unexpected time drift %d", now.Unix())
}
}
func TestFormat(t *testing.T) {
now := time.Now()
nowts := Timestamp(now)
if now.Format(time.ANSIC) != Format(nowts, time.ANSIC) {
t.Error("Format mismatch")
}
}
Loading…
Cancel
Save