This commit is contained in:
fibonacci1729 2018-03-14 10:03:32 -06:00
Родитель 7f24ce99ec
Коммит 464c2db1bb
12 изменённых файлов: 245 добавлений и 32 удалений

3
Gopkg.lock сгенерированный
Просмотреть файл

@ -1003,6 +1003,7 @@
"pkg/storage/driver",
"pkg/strvals",
"pkg/tiller/environment",
"pkg/timeconv",
"pkg/version"
]
revision = "8478fb4fc723885b155c924d1c8c410b7a9444e6"
@ -1198,6 +1199,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "9b343ab01789c05b43ebe52723821418095400f9265c33672540d8b7b8519350"
inputs-digest = "6a494ae6b3b1cd15b552e01be096cefd9d1268107fb6fde105098c91bbf15fa6"
solver-name = "gps-cdcl"
solver-version = 1

Просмотреть файл

@ -85,6 +85,7 @@ func newRootCmd(out io.Writer, in io.Reader) *cobra.Command {
newConnectCmd(out),
newDeleteCmd(out),
newLogsCmd(out),
newHistoryCmd(out),
)
// Find and add plugins

147
cmd/draft/history.go Normal file
Просмотреть файл

@ -0,0 +1,147 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/Azure/draft/pkg/draft/local"
"github.com/Azure/draft/pkg/storage"
"github.com/Azure/draft/pkg/storage/kube/configmap"
"github.com/ghodss/yaml"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"io"
"k8s.io/helm/pkg/timeconv"
)
const historyDesc = `Display the build history of a Draft application.`
type historyCmd struct {
out io.Writer
fmt string
env string
max int64
pretty bool
colWidth uint
}
func newHistoryCmd(out io.Writer) *cobra.Command {
hc := &historyCmd{out: out}
cmd := &cobra.Command{
Use: "history",
Short: historyDesc,
Long: historyDesc,
RunE: func(cmd *cobra.Command, args []string) error {
return hc.run()
},
}
f := cmd.Flags()
f.Int64Var(&hc.max, "max", 256, "maximum number of results to include in history")
f.UintVar(&hc.colWidth, "col-width", 60, "specifies the max column width of output")
f.BoolVar(&hc.pretty, "pretty", false, "pretty print output")
f.StringVarP(&hc.fmt, "output", "o", "table", "prints the output in the specified format (json|table|yaml)")
f.StringVarP(&hc.env, environmentFlagName, environmentFlagShorthand, defaultDraftEnvironment(), environmentFlagUsage)
return cmd
}
func (cmd *historyCmd) run() error {
app, err := local.DeployedApplication(draftToml, cmd.env)
if err != nil {
return err
}
client, _, err := getKubeClient(kubeContext)
if err != nil {
return fmt.Errorf("Could not get a kube client: %v", err)
}
store := configmap.NewConfigMaps(client.CoreV1().ConfigMaps(tillerNamespace))
// get history from store
h, err := getHistory(context.Background(), store, app.Name, cmd.max)
if err != nil {
return err
}
if len(h) > 0 {
var output []byte
switch bh := toBuildHistory(h); cmd.fmt {
case "yaml":
if output, err = yaml.Marshal(&bh); err != nil {
return err
}
case "json":
if output, err = json.Marshal(&bh); err != nil {
return err
}
if cmd.pretty {
var b bytes.Buffer
json.Indent(&b, output, "", " ")
output = b.Bytes()
}
case "table":
output = formatTable(bh, cmd.colWidth)
default:
return fmt.Errorf("unknown output format %q", cmd.fmt)
}
fmt.Fprintln(cmd.out, string(output))
}
return nil
}
func getHistory(ctx context.Context, store storage.Store, app string, max int64) (h []*storage.Object, err error) {
if h, err = store.GetBuilds(ctx, app); err != nil {
return nil, fmt.Errorf("failed to retrieve application (%q) build history from storage: %v", app, err)
}
// For deterministic return of history results we sort by the storage
// object's created at timestamp.
storage.SortByCreatedAt(h)
min := func(x, y int) int {
if x < y {
return x
}
return y
}
return h[:min(len(h), int(max))], nil
}
type buildHistory []buildInfo
type buildInfo struct {
BuildID string `json:"buildID"`
Release string `json:"release"`
Context string `json:"context"`
Created string `json:"createdAt"`
}
func toBuildHistory(ls []*storage.Object) (h buildHistory) {
orElse := func(str, def string) string {
if str != "" {
return str
}
return def
}
for i := len(ls) - 1; i >= 0; i-- {
rls := orElse(ls[i].GetRelease(), "-")
ctx := ls[i].GetContextID()
h = append(h, buildInfo{
BuildID: ls[i].GetBuildID(),
Release: rls,
Context: fmt.Sprintf("%X", ctx[len(ctx)-5:]),
Created: timeconv.String(ls[i].GetCreatedAt()),
})
}
return h
}
func formatTable(h buildHistory, w uint) []byte {
tbl := uitable.New()
tbl.MaxColWidth = w
tbl.AddRow("BUILD_ID", "CONTEXT_ID", "CREATED_AT", "RELEASE")
for i := 0; i < len(h); i++ {
b := h[i]
tbl.AddRow(b.BuildID, b.Context, b.Created, b.Release)
}
return tbl.Bytes()
}

Просмотреть файл

@ -480,6 +480,7 @@ func (b *Builder) release(ctx context.Context, app *AppContext, out chan<- *Summ
if err != nil {
return fmt.Errorf("could not install release: %v", err)
}
app.obj.Release = rls.Release.Name
formatReleaseStatus(app, rls.Release, summary)
} else {

14
pkg/storage/Makefile Normal file
Просмотреть файл

@ -0,0 +1,14 @@
google_deps = Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp
includes = ../../vendor/protobuf-include/include/
target = go
plugins =
deps = $(google_deps)
dst = .
.PHONY: proto
proto:
protoc -I=$(includes):. --$(target)_out=plugins=$(plugins),$(deps):$(dst) *.proto
.PHONY: clean
clean:
rm *.pb.go

Просмотреть файл

@ -2,8 +2,9 @@ package inprocess
import (
"context"
"github.com/Azure/draft/pkg/storage"
"github.com/golang/protobuf/ptypes"
"time"
)
// Store is an inprocess storage engine for draft.
@ -58,6 +59,11 @@ func (s *Store) CreateBuild(ctx context.Context, appName string, build *storage.
if _, ok := s.builds[appName]; ok {
return storage.NewErrAppStorageExists(appName)
}
now, err := ptypes.TimestampProto(time.Now())
if err != nil {
return err
}
build.CreatedAt = now
s.builds[appName] = []*storage.Object{build}
return nil
}
@ -68,10 +74,13 @@ func (s *Store) CreateBuild(ctx context.Context, appName string, build *storage.
// is updated.
//
// UpdateBuild implements storage.Updater.
func (s *Store) UpdateBuild(ctx context.Context, appName string, build *storage.Object) error {
func (s *Store) UpdateBuild(ctx context.Context, appName string, build *storage.Object) (err error) {
if _, ok := s.builds[appName]; !ok {
return s.CreateBuild(ctx, appName, build)
}
if build.CreatedAt, err = ptypes.TimestampProto(time.Now()); err != nil {
return err
}
s.builds[appName] = append(s.builds[appName], build)
// TODO(fibonacci1729): deduplication of builds.
return nil

Просмотреть файл

@ -2,10 +2,10 @@ package inprocess
import (
"context"
"github.com/Azure/draft/pkg/storage"
"github.com/golang/protobuf/ptypes"
"reflect"
"testing"
"github.com/Azure/draft/pkg/storage"
)
func TestStoreDeleteBuilds(t *testing.T) {
@ -42,7 +42,7 @@ func TestStoreDeleteBuild(t *testing.T) {
func TestStoreCreateBuild(t *testing.T) {
var (
build = &storage.Object{BuildID: "foo", Release: "bar", ContextID: []byte("foobar")}
build = objectStub("foo", "bar", []byte("foobar"))
store = NewStoreWithMocks()
ctx = context.TODO()
)
@ -63,7 +63,7 @@ func TestStoreCreateBuild(t *testing.T) {
func TestStoreUpdateBuild(t *testing.T) {
var (
build = &storage.Object{BuildID: "foo", Release: "bar", ContextID: []byte("foobar")}
build = objectStub("foo", "bar", []byte("foobar"))
store = NewStoreWithMocks()
ctx = context.TODO()
)
@ -109,11 +109,7 @@ func TestStoreGetBuild(t *testing.T) {
if err != nil {
t.Fatalf("could not get build: %v", err)
}
assertEqual(t, "GetBuild", obj, &storage.Object{
BuildID: "foo1",
Release: "bar1",
ContextID: []byte("foobar1"),
})
assertEqual(t, "GetBuild", obj, objectStub("foo1", "bar1", []byte("foobar1")))
// try fetching a build with an unknown appID; should fail.
if alt, err := store.GetBuild(ctx, "bad", ""); err == nil {
t.Fatalf("want err != nil; got alt: %+v", alt)
@ -142,5 +138,8 @@ func objectStub(buildID, release string, contextID []byte) *storage.Object {
BuildID: buildID,
Release: release,
ContextID: contextID,
CreatedAt: createdAt,
}
}
var createdAt = ptypes.TimestampNow()

Просмотреть файл

@ -2,8 +2,10 @@ package configmap
import (
"context"
"time"
"github.com/Azure/draft/pkg/storage"
"github.com/golang/protobuf/ptypes"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -64,6 +66,12 @@ func (s *ConfigMaps) DeleteBuild(ctx context.Context, appName, buildID string) (
//
// CreateBuild implements storage.Creater.
func (s *ConfigMaps) CreateBuild(ctx context.Context, appName string, build *storage.Object) error {
now, err := ptypes.TimestampProto(time.Now())
if err != nil {
return err
}
build.CreatedAt = now
cfgmap, err := newConfigMap(appName, build)
if err != nil {
return err
@ -94,6 +102,9 @@ func (s *ConfigMaps) UpdateBuild(ctx context.Context, appName string, build *sto
if _, ok := cfgmap.Data[build.BuildID]; ok {
return storage.NewErrAppBuildExists(appName, build.BuildID)
}
if build.CreatedAt, err = ptypes.TimestampProto(time.Now()); err != nil {
return err
}
content, err := storage.EncodeToString(build)
if err != nil {
return err

Просмотреть файл

@ -15,6 +15,7 @@ package storage
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/timestamp"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@ -29,10 +30,11 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// Object is the storage object for a draft applications build history.
type Object struct {
BuildID string `protobuf:"bytes,1,opt,name=buildID" json:"buildID,omitempty"`
Release string `protobuf:"bytes,2,opt,name=release" json:"release,omitempty"`
ContextID []byte `protobuf:"bytes,3,opt,name=contextID,proto3" json:"contextID,omitempty"`
LogsFileRef string `protobuf:"bytes,4,opt,name=logs_file_ref,json=logsFileRef" json:"logs_file_ref,omitempty"`
BuildID string `protobuf:"bytes,1,opt,name=buildID" json:"buildID,omitempty"`
Release string `protobuf:"bytes,2,opt,name=release" json:"release,omitempty"`
ContextID []byte `protobuf:"bytes,3,opt,name=contextID,proto3" json:"contextID,omitempty"`
LogsFileRef string `protobuf:"bytes,4,opt,name=logs_file_ref,json=logsFileRef" json:"logs_file_ref,omitempty"`
CreatedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt" json:"created_at,omitempty"`
}
func (m *Object) Reset() { *m = Object{} }
@ -68,6 +70,13 @@ func (m *Object) GetLogsFileRef() string {
return ""
}
func (m *Object) GetCreatedAt() *google_protobuf.Timestamp {
if m != nil {
return m.CreatedAt
}
return nil
}
func init() {
proto.RegisterType((*Object)(nil), "storage.Object")
}
@ -75,15 +84,18 @@ func init() {
func init() { proto.RegisterFile("object.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 146 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc9, 0x4f, 0xca, 0x4a,
0x4d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2f, 0x2e, 0xc9, 0x2f, 0x4a, 0x4c,
0x4f, 0x55, 0xaa, 0xe3, 0x62, 0xf3, 0x07, 0x4b, 0x08, 0x49, 0x70, 0xb1, 0x27, 0x95, 0x66, 0xe6,
0xa4, 0x78, 0xba, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8, 0x20, 0x99, 0xa2, 0xd4,
0x9c, 0xd4, 0xc4, 0xe2, 0x54, 0x09, 0x26, 0x88, 0x0c, 0x94, 0x2b, 0x24, 0xc3, 0xc5, 0x99, 0x9c,
0x9f, 0x57, 0x92, 0x5a, 0x51, 0xe2, 0xe9, 0x22, 0xc1, 0xac, 0xc0, 0xa8, 0xc1, 0x13, 0x84, 0x10,
0x10, 0x52, 0xe2, 0xe2, 0xcd, 0xc9, 0x4f, 0x2f, 0x8e, 0x4f, 0xcb, 0xcc, 0x49, 0x8d, 0x2f, 0x4a,
0x4d, 0x93, 0x60, 0x01, 0xeb, 0xe6, 0x06, 0x09, 0xba, 0x65, 0xe6, 0xa4, 0x06, 0xa5, 0xa6, 0x25,
0xb1, 0x81, 0xdd, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x73, 0xd8, 0xd6, 0x04, 0x9f, 0x00,
0x00, 0x00,
// 208 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x8e, 0xbd, 0x6a, 0xc3, 0x30,
0x14, 0x85, 0x51, 0x7f, 0x12, 0xac, 0xa4, 0x8b, 0x26, 0x11, 0x0a, 0x35, 0x99, 0x3c, 0x29, 0xd0,
0x4e, 0x1d, 0x0b, 0xa1, 0x90, 0xa9, 0x20, 0xba, 0x1b, 0xc9, 0xb9, 0x12, 0x2a, 0x72, 0xaf, 0x91,
0xae, 0xa1, 0xaf, 0xd6, 0xb7, 0x2b, 0x96, 0x6d, 0x3a, 0x9e, 0xf3, 0x9d, 0x03, 0x1f, 0xdf, 0xa3,
0xfd, 0x82, 0x8e, 0xd4, 0x90, 0x90, 0x50, 0x6c, 0x33, 0x61, 0x32, 0x1e, 0x0e, 0x4f, 0x1e, 0xd1,
0x47, 0x38, 0x95, 0xda, 0x8e, 0xee, 0x44, 0xa1, 0x87, 0x4c, 0xa6, 0x1f, 0xe6, 0xe5, 0xf1, 0x97,
0xf1, 0xcd, 0x47, 0xb9, 0x0a, 0xc9, 0xb7, 0x76, 0x0c, 0xf1, 0x7a, 0x39, 0x4b, 0x56, 0xb3, 0xa6,
0xd2, 0x6b, 0x9c, 0x48, 0x82, 0x08, 0x26, 0x83, 0xbc, 0x99, 0xc9, 0x12, 0xc5, 0x23, 0xaf, 0x3a,
0xfc, 0x26, 0xf8, 0xa1, 0xcb, 0x59, 0xde, 0xd6, 0xac, 0xd9, 0xeb, 0xff, 0x42, 0x1c, 0xf9, 0x43,
0x44, 0x9f, 0x5b, 0x17, 0x22, 0xb4, 0x09, 0x9c, 0xbc, 0x2b, 0xef, 0xdd, 0x54, 0xbe, 0x87, 0x08,
0x1a, 0x9c, 0x78, 0xe5, 0xbc, 0x4b, 0x60, 0x08, 0xae, 0xad, 0x21, 0x79, 0x5f, 0xb3, 0x66, 0xf7,
0x7c, 0x50, 0xb3, 0xb6, 0x5a, 0xb5, 0xd5, 0xe7, 0xaa, 0xad, 0xab, 0x65, 0xfd, 0x46, 0x76, 0x53,
0xf0, 0xcb, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa7, 0xa5, 0xc3, 0xdf, 0xfc, 0x00, 0x00, 0x00,
}

Просмотреть файл

@ -2,10 +2,13 @@ syntax = "proto3";
package storage;
import "google/protobuf/timestamp.proto";
// Object is the storage object for a draft applications build history.
message Object {
string buildID = 1; // build uuid generated during draft up
string release = 2; // name of associated helm release
bytes contextID = 3; // checksum of docker context
string logs_file_ref = 4; // reference to build logs file
string buildID = 1; // build uuid generated during draft up
string release = 2; // name of associated helm release
bytes contextID = 3; // checksum of docker context
string logs_file_ref = 4; // reference to build logs file
google.protobuf.Timestamp created_at = 5; // time at which this object was created
}

15
pkg/storage/sorter.go Normal file
Просмотреть файл

@ -0,0 +1,15 @@
package storage
import (
"sort"
)
// SortByCreatedAt returns the list of storage objects sorted by an
// object's created at timestamp (in seconds).
func SortByCreatedAt(objs []*Object) {
sort.SliceStable(objs, func(i, j int) bool {
ti := objs[i].GetCreatedAt().GetSeconds()
tj := objs[j].GetCreatedAt().GetSeconds()
return ti < tj
})
}

Просмотреть файл

@ -3,7 +3,7 @@ package storage
// To regenerate the protocol buffer types for this package, run:
// go generate
//go:generate protoc object.proto --go_out=.
//go:generate make proto
import (
"context"