зеркало из https://github.com/Azure/draft-classic.git
draft history
This commit is contained in:
Родитель
7f24ce99ec
Коммит
464c2db1bb
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче