Commit 58c05f87 authored by Michelle Noorali's avatar Michelle Noorali

feat(*): stream helm test messages to client

parent d46d63a8
...@@ -21,7 +21,7 @@ import "google/protobuf/timestamp.proto"; ...@@ -21,7 +21,7 @@ import "google/protobuf/timestamp.proto";
option go_package = "release"; option go_package = "release";
message TestResult { message TestRun {
enum Status { enum Status {
UNKNOWN = 0; UNKNOWN = 0;
SUCCESS = 1; SUCCESS = 1;
...@@ -31,5 +31,6 @@ message TestResult { ...@@ -31,5 +31,6 @@ message TestResult {
string name = 1; string name = 1;
Status status = 2; Status status = 2;
string info = 3; string info = 3;
google.protobuf.Timestamp last_run = 4; google.protobuf.Timestamp started_at = 4;
google.protobuf.Timestamp completed_at = 5;
} }
...@@ -17,15 +17,18 @@ syntax = "proto3"; ...@@ -17,15 +17,18 @@ syntax = "proto3";
package hapi.release; package hapi.release;
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
import "hapi/release/test_result.proto"; import "hapi/release/test_run.proto";
option go_package = "release"; option go_package = "release";
// TestSuite comprises of the last run of the pre-defined test suite of a release version // TestSuite comprises of the last run of the pre-defined test suite of a release version
message TestSuite { message TestSuite {
// LastRun indicates the date/time this test was last run. // StartedAt indicates the date/time this test suite was kicked off
google.protobuf.Timestamp last_run = 1; google.protobuf.Timestamp started_at = 1;
// CompletedAt indicates the date/time this test suite was completed
google.protobuf.Timestamp completed_at = 2;
// Results are the results of each segment of the test // Results are the results of each segment of the test
repeated hapi.release.TestResult results = 2; repeated hapi.release.TestRun results = 3;
} }
...@@ -22,7 +22,6 @@ import "hapi/release/release.proto"; ...@@ -22,7 +22,6 @@ import "hapi/release/release.proto";
import "hapi/release/info.proto"; import "hapi/release/info.proto";
import "hapi/release/status.proto"; import "hapi/release/status.proto";
import "hapi/version/version.proto"; import "hapi/version/version.proto";
import "hapi/release/test_suite.proto";
option go_package = "services"; option go_package = "services";
...@@ -82,7 +81,7 @@ service ReleaseService { ...@@ -82,7 +81,7 @@ service ReleaseService {
//TODO: move this to a test release service or rename to RunReleaseTest //TODO: move this to a test release service or rename to RunReleaseTest
// TestRelease runs the tests for a given release // TestRelease runs the tests for a given release
rpc RunReleaseTest(TestReleaseRequest) returns (TestReleaseResponse) { rpc RunReleaseTest(TestReleaseRequest) returns (stream TestReleaseResponse) {
} }
} }
...@@ -322,5 +321,5 @@ message TestReleaseRequest { ...@@ -322,5 +321,5 @@ message TestReleaseRequest {
// TestReleaseResponse // TestReleaseResponse
message TestReleaseResponse { message TestReleaseResponse {
// TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream)
hapi.release.TestSuite result = 1; string msg = 1;
} }
...@@ -20,11 +20,9 @@ import ( ...@@ -20,11 +20,9 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm"
//"k8s.io/helm/pkg/proto/hapi/release"
) )
const releaseTestDesc = ` const releaseTestDesc = `
...@@ -69,20 +67,20 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command { ...@@ -69,20 +67,20 @@ func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command {
return cmd return cmd
} }
func (t *releaseTestCmd) run() error { func (t *releaseTestCmd) run() (err error) {
res, err := t.client.ReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout)) c, errc := t.client.RunReleaseTest(t.name, helm.ReleaseTestTimeout(t.timeout))
if err != nil {
for {
select {
case err := <-errc:
return prettyError(err) return prettyError(err)
case res, ok := <-c:
if !ok {
break
}
fmt.Fprintf(t.out, res.Msg+"\n")
} }
table := uitable.New()
table.MaxColWidth = 50
table.AddRow("NAME", "Result", "Info")
//TODO: change Result to Suite
for _, r := range res.Result.Results {
table.AddRow(r.Name, r.Status, r.Info)
} }
fmt.Fprintln(t.out, table.String()) //TODO: or no tests found
return nil return nil
} }
...@@ -17,6 +17,8 @@ limitations under the License. ...@@ -17,6 +17,8 @@ limitations under the License.
package helm // import "k8s.io/helm/pkg/helm" package helm // import "k8s.io/helm/pkg/helm"
import ( import (
"io"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
...@@ -244,8 +246,8 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get ...@@ -244,8 +246,8 @@ func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.Get
return h.history(ctx, req) return h.history(ctx, req)
} }
// ReleaseTest executes a pre-defined test on a release //ReleaseTest executes a pre-defined test on a release
func (h *Client) ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error) { func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) {
for _, opt := range opts { for _, opt := range opts {
opt(&h.opts) opt(&h.opts)
} }
...@@ -371,13 +373,39 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls. ...@@ -371,13 +373,39 @@ func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.
} }
// Executes tiller.TestRelease RPC. // Executes tiller.TestRelease RPC.
func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (*rls.TestReleaseResponse, error) { func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) {
errc := make(chan error, 1)
c, err := grpc.Dial(h.opts.host, grpc.WithInsecure()) c, err := grpc.Dial(h.opts.host, grpc.WithInsecure())
if err != nil { if err != nil {
return nil, err errc <- err
return nil, errc
} }
ch := make(chan *rls.TestReleaseResponse, 1)
go func() {
defer close(errc)
defer close(ch)
defer c.Close() defer c.Close()
rlc := rls.NewReleaseServiceClient(c) rlc := rls.NewReleaseServiceClient(c)
return rlc.RunReleaseTest(ctx, req) s, err := rlc.RunReleaseTest(ctx, req)
if err != nil {
errc <- err
return
}
for {
msg, err := s.Recv()
if err == io.EOF {
return
}
if err != nil {
errc <- err
return
}
ch <- msg
}
}()
return ch, errc
} }
...@@ -34,5 +34,5 @@ type Interface interface { ...@@ -34,5 +34,5 @@ type Interface interface {
ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error)
ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error)
GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error)
ReleaseTest(rlsName string, opts ...ReleaseTestOption) (*rls.TestReleaseResponse, error) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error)
} }
...@@ -10,7 +10,7 @@ It is generated from these files: ...@@ -10,7 +10,7 @@ It is generated from these files:
hapi/release/info.proto hapi/release/info.proto
hapi/release/release.proto hapi/release/release.proto
hapi/release/status.proto hapi/release/status.proto
hapi/release/test_result.proto hapi/release/test_run.proto
hapi/release/test_suite.proto hapi/release/test_suite.proto
It has these top-level messages: It has these top-level messages:
...@@ -18,7 +18,7 @@ It has these top-level messages: ...@@ -18,7 +18,7 @@ It has these top-level messages:
Info Info
Release Release
Status Status
TestResult TestRun
TestSuite TestSuite
*/ */
package release package release
......
// Code generated by protoc-gen-go.
// source: hapi/release/test_result.proto
// DO NOT EDIT!
package release
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
var _ = fmt.Errorf
var _ = math.Inf
type TestResult_Status int32
const (
TestResult_UNKNOWN TestResult_Status = 0
TestResult_SUCCESS TestResult_Status = 1
TestResult_FAILURE TestResult_Status = 2
)
var TestResult_Status_name = map[int32]string{
0: "UNKNOWN",
1: "SUCCESS",
2: "FAILURE",
}
var TestResult_Status_value = map[string]int32{
"UNKNOWN": 0,
"SUCCESS": 1,
"FAILURE": 2,
}
func (x TestResult_Status) String() string {
return proto.EnumName(TestResult_Status_name, int32(x))
}
func (TestResult_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} }
type TestResult struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Status TestResult_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestResult_Status" json:"status,omitempty"`
Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"`
LastRun *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=last_run,json=lastRun" json:"last_run,omitempty"`
}
func (m *TestResult) Reset() { *m = TestResult{} }
func (m *TestResult) String() string { return proto.CompactTextString(m) }
func (*TestResult) ProtoMessage() {}
func (*TestResult) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
func (m *TestResult) GetLastRun() *google_protobuf.Timestamp {
if m != nil {
return m.LastRun
}
return nil
}
func init() {
proto.RegisterType((*TestResult)(nil), "hapi.release.TestResult")
proto.RegisterEnum("hapi.release.TestResult_Status", TestResult_Status_name, TestResult_Status_value)
}
func init() { proto.RegisterFile("hapi/release/test_result.proto", fileDescriptor4) }
var fileDescriptor4 = []byte{
// 244 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x41, 0x4b, 0xc3, 0x30,
0x18, 0x86, 0xcd, 0x1c, 0xad, 0xcb, 0x44, 0x4a, 0x4e, 0x65, 0x07, 0x57, 0x76, 0xea, 0x29, 0x81,
0x89, 0x78, 0xd6, 0x31, 0x41, 0x94, 0x0a, 0xe9, 0x8a, 0xe0, 0x45, 0x32, 0xf8, 0x36, 0x0b, 0x6d,
0x53, 0x9a, 0x2f, 0x3f, 0xd5, 0xff, 0x23, 0x49, 0x5a, 0xf4, 0xf6, 0xbd, 0xbc, 0x6f, 0x9e, 0x3c,
0xf4, 0xf6, 0x5b, 0xf5, 0xb5, 0x18, 0xa0, 0x01, 0x65, 0x40, 0x20, 0x18, 0xfc, 0x1a, 0xc0, 0xd8,
0x06, 0x79, 0x3f, 0x68, 0xd4, 0xec, 0xda, 0xf5, 0x7c, 0xec, 0x57, 0xeb, 0xb3, 0xd6, 0xe7, 0x06,
0x84, 0xef, 0x8e, 0xf6, 0x24, 0xb0, 0x6e, 0xc1, 0xa0, 0x6a, 0xfb, 0x30, 0xdf, 0xfc, 0x10, 0x4a,
0x0f, 0x60, 0x50, 0x7a, 0x06, 0x63, 0x74, 0xde, 0xa9, 0x16, 0x52, 0x92, 0x91, 0x7c, 0x21, 0xfd,
0xcd, 0x1e, 0x68, 0x64, 0x50, 0xa1, 0x35, 0xe9, 0x2c, 0x23, 0xf9, 0xcd, 0x76, 0xcd, 0xff, 0x7f,
0xc1, 0xff, 0x5e, 0xf3, 0xd2, 0xcf, 0xe4, 0x38, 0x77, 0xb0, 0xba, 0x3b, 0xe9, 0xf4, 0x32, 0xc0,
0xdc, 0xcd, 0xee, 0xe9, 0x55, 0xa3, 0x9c, 0xb3, 0xed, 0xd2, 0x79, 0x46, 0xf2, 0xe5, 0x76, 0xc5,
0x83, 0x23, 0x9f, 0x1c, 0xf9, 0x61, 0x72, 0x94, 0xb1, 0xdb, 0x4a, 0xdb, 0x6d, 0x04, 0x8d, 0x02,
0x9c, 0x2d, 0x69, 0x5c, 0x15, 0xaf, 0xc5, 0xfb, 0x47, 0x91, 0x5c, 0xb8, 0x50, 0x56, 0xbb, 0xdd,
0xbe, 0x2c, 0x13, 0xe2, 0xc2, 0xf3, 0xe3, 0xcb, 0x5b, 0x25, 0xf7, 0xc9, 0xec, 0x69, 0xf1, 0x19,
0x8f, 0x82, 0xc7, 0xc8, 0x83, 0xef, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0x4c, 0x44, 0x22, 0xbb,
0x3a, 0x01, 0x00, 0x00,
}
// Code generated by protoc-gen-go.
// source: hapi/release/test_run.proto
// DO NOT EDIT!
package release
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
var _ = fmt.Errorf
var _ = math.Inf
type TestRun_Status int32
const (
TestRun_UNKNOWN TestRun_Status = 0
TestRun_SUCCESS TestRun_Status = 1
TestRun_FAILURE TestRun_Status = 2
)
var TestRun_Status_name = map[int32]string{
0: "UNKNOWN",
1: "SUCCESS",
2: "FAILURE",
}
var TestRun_Status_value = map[string]int32{
"UNKNOWN": 0,
"SUCCESS": 1,
"FAILURE": 2,
}
func (x TestRun_Status) String() string {
return proto.EnumName(TestRun_Status_name, int32(x))
}
func (TestRun_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} }
type TestRun struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Status TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,omitempty"`
Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"`
StartedAt *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=started_at,json=startedAt" json:"started_at,omitempty"`
CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"`
}
func (m *TestRun) Reset() { *m = TestRun{} }
func (m *TestRun) String() string { return proto.CompactTextString(m) }
func (*TestRun) ProtoMessage() {}
func (*TestRun) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
func (m *TestRun) GetStartedAt() *google_protobuf.Timestamp {
if m != nil {
return m.StartedAt
}
return nil
}
func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp {
if m != nil {
return m.CompletedAt
}
return nil
}
func init() {
proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun")
proto.RegisterEnum("hapi.release.TestRun_Status", TestRun_Status_name, TestRun_Status_value)
}
func init() { proto.RegisterFile("hapi/release/test_run.proto", fileDescriptor4) }
var fileDescriptor4 = []byte{
// 265 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0x41, 0x4b, 0xfb, 0x40,
0x14, 0xc4, 0xff, 0xc9, 0xbf, 0x26, 0x64, 0x53, 0x24, 0xec, 0x29, 0x54, 0xc1, 0xd0, 0x53, 0x4e,
0xbb, 0x50, 0xbd, 0x78, 0xf0, 0x10, 0x4b, 0x05, 0x51, 0x22, 0x6c, 0x1a, 0x04, 0x2f, 0x65, 0xab,
0xaf, 0x35, 0x90, 0x64, 0x43, 0xf6, 0xe5, 0x8b, 0xf8, 0x89, 0x65, 0x93, 0xad, 0x78, 0xf3, 0xf6,
0x86, 0xf9, 0xcd, 0x30, 0x8f, 0x5c, 0x7c, 0xca, 0xae, 0xe2, 0x3d, 0xd4, 0x20, 0x35, 0x70, 0x04,
0x8d, 0xbb, 0x7e, 0x68, 0x59, 0xd7, 0x2b, 0x54, 0x74, 0x6e, 0x4c, 0x66, 0xcd, 0xc5, 0xd5, 0x51,
0xa9, 0x63, 0x0d, 0x7c, 0xf4, 0xf6, 0xc3, 0x81, 0x63, 0xd5, 0x80, 0x46, 0xd9, 0x74, 0x13, 0xbe,
0xfc, 0x72, 0x89, 0xbf, 0x05, 0x8d, 0x62, 0x68, 0x29, 0x25, 0xb3, 0x56, 0x36, 0x10, 0x3b, 0x89,
0x93, 0x06, 0x62, 0xbc, 0xe9, 0x0d, 0xf1, 0x34, 0x4a, 0x1c, 0x74, 0xec, 0x26, 0x4e, 0x7a, 0xbe,
0xba, 0x64, 0xbf, 0xfb, 0x99, 0x8d, 0xb2, 0x62, 0x64, 0x84, 0x65, 0x4d, 0x53, 0xd5, 0x1e, 0x54,
0xfc, 0x7f, 0x6a, 0x32, 0x37, 0xbd, 0x25, 0x44, 0xa3, 0xec, 0x11, 0x3e, 0x76, 0x12, 0xe3, 0x59,
0xe2, 0xa4, 0xe1, 0x6a, 0xc1, 0xa6, 0x7d, 0xec, 0xb4, 0x8f, 0x6d, 0x4f, 0xfb, 0x44, 0x60, 0xe9,
0x0c, 0xe9, 0x1d, 0x99, 0xbf, 0xab, 0xa6, 0xab, 0xc1, 0x86, 0xcf, 0xfe, 0x0c, 0x87, 0x3f, 0x7c,
0x86, 0x4b, 0x4e, 0xbc, 0x69, 0x1f, 0x0d, 0x89, 0x5f, 0xe6, 0x4f, 0xf9, 0xcb, 0x6b, 0x1e, 0xfd,
0x33, 0xa2, 0x28, 0xd7, 0xeb, 0x4d, 0x51, 0x44, 0x8e, 0x11, 0x0f, 0xd9, 0xe3, 0x73, 0x29, 0x36,
0x91, 0x7b, 0x1f, 0xbc, 0xf9, 0xf6, 0xc1, 0xbd, 0x37, 0x96, 0x5f, 0x7f, 0x07, 0x00, 0x00, 0xff,
0xff, 0x8d, 0xb9, 0xce, 0x57, 0x74, 0x01, 0x00, 0x00,
}
...@@ -16,10 +16,12 @@ var _ = math.Inf ...@@ -16,10 +16,12 @@ var _ = math.Inf
// TestSuite comprises of the last run of the pre-defined test suite of a release version // TestSuite comprises of the last run of the pre-defined test suite of a release version
type TestSuite struct { type TestSuite struct {
// LastRun indicates the date/time this test was last run. // StartedAt indicates the date/time this test suite was kicked off
LastRun *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` StartedAt *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=started_at,json=startedAt" json:"started_at,omitempty"`
// CompletedAt indicates the date/time this test suite was completed
CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"`
// Results are the results of each segment of the test // Results are the results of each segment of the test
Results []*TestResult `protobuf:"bytes,2,rep,name=results" json:"results,omitempty"` Results []*TestRun `protobuf:"bytes,3,rep,name=results" json:"results,omitempty"`
} }
func (m *TestSuite) Reset() { *m = TestSuite{} } func (m *TestSuite) Reset() { *m = TestSuite{} }
...@@ -27,14 +29,21 @@ func (m *TestSuite) String() string { return proto.CompactTextString( ...@@ -27,14 +29,21 @@ func (m *TestSuite) String() string { return proto.CompactTextString(
func (*TestSuite) ProtoMessage() {} func (*TestSuite) ProtoMessage() {}
func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} } func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} }
func (m *TestSuite) GetLastRun() *google_protobuf.Timestamp { func (m *TestSuite) GetStartedAt() *google_protobuf.Timestamp {
if m != nil { if m != nil {
return m.LastRun return m.StartedAt
} }
return nil return nil
} }
func (m *TestSuite) GetResults() []*TestResult { func (m *TestSuite) GetCompletedAt() *google_protobuf.Timestamp {
if m != nil {
return m.CompletedAt
}
return nil
}
func (m *TestSuite) GetResults() []*TestRun {
if m != nil { if m != nil {
return m.Results return m.Results
} }
...@@ -48,17 +57,18 @@ func init() { ...@@ -48,17 +57,18 @@ func init() {
func init() { proto.RegisterFile("hapi/release/test_suite.proto", fileDescriptor5) } func init() { proto.RegisterFile("hapi/release/test_suite.proto", fileDescriptor5) }
var fileDescriptor5 = []byte{ var fileDescriptor5 = []byte{
// 183 bytes of a gzipped FileDescriptorProto // 207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x64, 0x8e, 0xc1, 0x8a, 0x83, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4a, 0x86, 0x40,
0x14, 0x45, 0x71, 0x06, 0xc6, 0x31, 0xce, 0xca, 0x95, 0x08, 0xd3, 0x4a, 0x57, 0xae, 0x5e, 0xc0, 0x14, 0x85, 0x31, 0x21, 0x71, 0x74, 0x35, 0x10, 0x88, 0x11, 0x49, 0x2b, 0x57, 0x33, 0x60, 0xab,
0xd2, 0x1f, 0xe8, 0x27, 0xa4, 0xae, 0xba, 0x29, 0x11, 0x5e, 0xad, 0x10, 0x8d, 0xf8, 0x5e, 0xfa, 0x16, 0x2d, 0xec, 0x11, 0xcc, 0x55, 0x1b, 0x19, 0xeb, 0x66, 0xc2, 0xe8, 0x0c, 0x73, 0xef, 0xbc,
0xfd, 0x25, 0x46, 0xa1, 0xd0, 0xf5, 0x39, 0xdc, 0x73, 0xc5, 0xff, 0x43, 0x4f, 0xbd, 0x9c, 0xd1, 0x5a, 0xcf, 0x17, 0xea, 0x18, 0x41, 0x8b, 0x7f, 0xfd, 0x7d, 0xe7, 0x9c, 0x7b, 0xd9, 0xdd, 0x97,
0xa0, 0x26, 0x94, 0x8c, 0xc4, 0x37, 0x72, 0x3d, 0x23, 0x4c, 0xb3, 0x65, 0x9b, 0xfd, 0x79, 0x0c, 0xb2, 0xb3, 0x74, 0xa0, 0x41, 0x21, 0x48, 0x02, 0xa4, 0x01, 0xfd, 0x4c, 0x20, 0xac, 0x33, 0x64,
0x2b, 0x2e, 0xf6, 0x9d, 0xb5, 0x9d, 0x41, 0xb9, 0xb0, 0xd6, 0xdd, 0x25, 0xf7, 0x03, 0x12, 0xeb, 0x78, 0xbe, 0x61, 0x11, 0x70, 0x79, 0x3f, 0x19, 0x33, 0x69, 0x90, 0x3b, 0x1b, 0xfd, 0xa7, 0xa4,
0x61, 0x0a, 0x7a, 0xb1, 0xfb, 0x5c, 0x9b, 0x91, 0x9c, 0xe1, 0xc0, 0x0f, 0x4f, 0x91, 0x34, 0x48, 0x79, 0x01, 0x24, 0xb5, 0xd8, 0x43, 0x2f, 0x6f, 0xff, 0xb7, 0x39, 0xbf, 0x1e, 0xf0, 0xe1, 0x3b,
0x7c, 0xf1, 0x85, 0xec, 0x24, 0x7e, 0x8d, 0xf6, 0x86, 0x1b, 0xf3, 0xa8, 0x8c, 0xaa, 0xb4, 0x2e, 0x62, 0x69, 0x0f, 0x48, 0xaf, 0x5b, 0x3f, 0x7f, 0x62, 0x0c, 0x49, 0x39, 0x82, 0x8f, 0x41, 0x51,
0x20, 0x04, 0x60, 0x0b, 0x40, 0xb3, 0x05, 0x54, 0xec, 0x5d, 0xe5, 0xc6, 0xac, 0x16, 0x71, 0xd8, 0x11, 0x55, 0x51, 0x9d, 0x35, 0xa5, 0x38, 0x06, 0xc4, 0x39, 0x20, 0xfa, 0x73, 0xa0, 0x4b, 0x83,
0xa4, 0xfc, 0xab, 0xfc, 0xae, 0xd2, 0x3a, 0x87, 0xf7, 0x93, 0xe0, 0x03, 0x6a, 0x11, 0xd4, 0x26, 0xdd, 0x12, 0x7f, 0x66, 0xf9, 0xbb, 0x59, 0xac, 0x86, 0x10, 0xbe, 0xba, 0x18, 0xce, 0x7e, 0xfd,
0x9e, 0x93, 0x6b, 0xbc, 0xe2, 0xf6, 0x67, 0xd9, 0x3e, 0xbe, 0x02, 0x00, 0x00, 0xff, 0xff, 0x05, 0x96, 0xb8, 0x64, 0x89, 0x03, 0xf4, 0x9a, 0xb0, 0x88, 0xab, 0xb8, 0xce, 0x9a, 0x1b, 0xf1, 0xf7,
0x00, 0xf5, 0xbb, 0xf9, 0x00, 0x00, 0x00, 0x4b, 0xb1, 0xdd, 0xd8, 0xf9, 0xb5, 0x3b, 0xad, 0x97, 0xf4, 0x2d, 0x09, 0x6c, 0xbc, 0xde, 0xcb,
0x1f, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x59, 0x65, 0x4f, 0x37, 0x01, 0x00, 0x00,
} }
...@@ -42,7 +42,6 @@ import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release" ...@@ -42,7 +42,6 @@ import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release2 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release2 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release" import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release"
import hapi_version "k8s.io/helm/pkg/proto/hapi/version" import hapi_version "k8s.io/helm/pkg/proto/hapi/version"
import hapi_release4 "k8s.io/helm/pkg/proto/hapi/release"
import ( import (
context "golang.org/x/net/context" context "golang.org/x/net/context"
...@@ -507,7 +506,7 @@ func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0 ...@@ -507,7 +506,7 @@ func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0
// TestReleaseResponse // TestReleaseResponse
type TestReleaseResponse struct { type TestReleaseResponse struct {
// TODO: change to repeated hapi.release.Release.Test results = 1; (for stream) // TODO: change to repeated hapi.release.Release.Test results = 1; (for stream)
Result *hapi_release4.TestSuite `protobuf:"bytes,1,opt,name=result" json:"result,omitempty"` Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
} }
func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} } func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} }
...@@ -515,13 +514,6 @@ func (m *TestReleaseResponse) String() string { return proto.CompactT ...@@ -515,13 +514,6 @@ func (m *TestReleaseResponse) String() string { return proto.CompactT
func (*TestReleaseResponse) ProtoMessage() {} func (*TestReleaseResponse) ProtoMessage() {}
func (*TestReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } func (*TestReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} }
func (m *TestReleaseResponse) GetResult() *hapi_release4.TestSuite {
if m != nil {
return m.Result
}
return nil
}
func init() { func init() {
proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest")
proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort") proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort")
...@@ -582,7 +574,7 @@ type ReleaseServiceClient interface { ...@@ -582,7 +574,7 @@ type ReleaseServiceClient interface {
GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error) GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error)
// TODO: move this to a test release service or rename to RunReleaseTest // TODO: move this to a test release service or rename to RunReleaseTest
// TestRelease runs the tests for a given release // TestRelease runs the tests for a given release
RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (*TestReleaseResponse, error) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error)
} }
type releaseServiceClient struct { type releaseServiceClient struct {
...@@ -697,13 +689,36 @@ func (c *releaseServiceClient) GetHistory(ctx context.Context, in *GetHistoryReq ...@@ -697,13 +689,36 @@ func (c *releaseServiceClient) GetHistory(ctx context.Context, in *GetHistoryReq
return out, nil return out, nil
} }
func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (*TestReleaseResponse, error) { func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) {
out := new(TestReleaseResponse) stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[1], c.cc, "/hapi.services.tiller.ReleaseService/RunReleaseTest", opts...)
err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/RunReleaseTest", in, out, c.cc, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return out, nil x := &releaseServiceRunReleaseTestClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ReleaseService_RunReleaseTestClient interface {
Recv() (*TestReleaseResponse, error)
grpc.ClientStream
}
type releaseServiceRunReleaseTestClient struct {
grpc.ClientStream
}
func (x *releaseServiceRunReleaseTestClient) Recv() (*TestReleaseResponse, error) {
m := new(TestReleaseResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
} }
// Server API for ReleaseService service // Server API for ReleaseService service
...@@ -732,7 +747,7 @@ type ReleaseServiceServer interface { ...@@ -732,7 +747,7 @@ type ReleaseServiceServer interface {
GetHistory(context.Context, *GetHistoryRequest) (*GetHistoryResponse, error) GetHistory(context.Context, *GetHistoryRequest) (*GetHistoryResponse, error)
// TODO: move this to a test release service or rename to RunReleaseTest // TODO: move this to a test release service or rename to RunReleaseTest
// TestRelease runs the tests for a given release // TestRelease runs the tests for a given release
RunReleaseTest(context.Context, *TestReleaseRequest) (*TestReleaseResponse, error) RunReleaseTest(*TestReleaseRequest, ReleaseService_RunReleaseTestServer) error
} }
func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) { func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) {
...@@ -904,22 +919,25 @@ func _ReleaseService_GetHistory_Handler(srv interface{}, ctx context.Context, de ...@@ -904,22 +919,25 @@ func _ReleaseService_GetHistory_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _ReleaseService_RunReleaseTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _ReleaseService_RunReleaseTest_Handler(srv interface{}, stream grpc.ServerStream) error {
in := new(TestReleaseRequest) m := new(TestReleaseRequest)
if err := dec(in); err != nil { if err := stream.RecvMsg(m); err != nil {
return nil, err return err
}
if interceptor == nil {
return srv.(ReleaseServiceServer).RunReleaseTest(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/hapi.services.tiller.ReleaseService/RunReleaseTest",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ReleaseServiceServer).RunReleaseTest(ctx, req.(*TestReleaseRequest))
} }
return interceptor(ctx, in, info, handler) return srv.(ReleaseServiceServer).RunReleaseTest(m, &releaseServiceRunReleaseTestServer{stream})
}
type ReleaseService_RunReleaseTestServer interface {
Send(*TestReleaseResponse) error
grpc.ServerStream
}
type releaseServiceRunReleaseTestServer struct {
grpc.ServerStream
}
func (x *releaseServiceRunReleaseTestServer) Send(m *TestReleaseResponse) error {
return x.ServerStream.SendMsg(m)
} }
var _ReleaseService_serviceDesc = grpc.ServiceDesc{ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
...@@ -958,10 +976,6 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ ...@@ -958,10 +976,6 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
MethodName: "GetHistory", MethodName: "GetHistory",
Handler: _ReleaseService_GetHistory_Handler, Handler: _ReleaseService_GetHistory_Handler,
}, },
{
MethodName: "RunReleaseTest",
Handler: _ReleaseService_RunReleaseTest_Handler,
},
}, },
Streams: []grpc.StreamDesc{ Streams: []grpc.StreamDesc{
{ {
...@@ -969,6 +983,11 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ ...@@ -969,6 +983,11 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{
Handler: _ReleaseService_ListReleases_Handler, Handler: _ReleaseService_ListReleases_Handler,
ServerStreams: true, ServerStreams: true,
}, },
{
StreamName: "RunReleaseTest",
Handler: _ReleaseService_RunReleaseTest_Handler,
ServerStreams: true,
},
}, },
Metadata: fileDescriptor0, Metadata: fileDescriptor0,
} }
......
...@@ -137,6 +137,7 @@ type KubeClient interface { ...@@ -137,6 +137,7 @@ type KubeClient interface {
Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error Update(namespace string, originalReader, modifiedReader io.Reader, recreate bool, timeout int64, shouldWait bool) error
Build(namespace string, reader io.Reader) (kube.Result, error) Build(namespace string, reader io.Reader) (kube.Result, error)
//TODO: insert description //TODO: insert description
WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error)
} }
...@@ -184,6 +185,10 @@ func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, er ...@@ -184,6 +185,10 @@ func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, er
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }
func (p *PrintingKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
return "", nil
}
// Environment provides the context for executing a client request. // Environment provides the context for executing a client request.
// //
// All services in a context are concurrency safe. // All services in a context are concurrency safe.
......
...@@ -20,10 +20,12 @@ import ( ...@@ -20,10 +20,12 @@ import (
"bytes" "bytes"
"io" "io"
"testing" "testing"
"time"
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
) )
...@@ -56,6 +58,10 @@ func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) ...@@ -56,6 +58,10 @@ func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error)
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }
func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (api.PodPhase, error) {
return "", nil
}
var _ Engine = &mockEngine{} var _ Engine = &mockEngine{}
var _ KubeClient = &mockKubeClient{} var _ KubeClient = &mockKubeClient{}
var _ KubeClient = &PrintingKubeClient{} var _ KubeClient = &PrintingKubeClient{}
......
...@@ -1066,27 +1066,27 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro ...@@ -1066,27 +1066,27 @@ func validateManifest(c environment.KubeClient, ns string, manifest []byte) erro
} }
// RunTestRelease runs a pre-defined test on a given release // RunTestRelease runs a pre-defined test on a given release
func (s *ReleaseServer) RunReleaseTest(c ctx.Context, req *services.TestReleaseRequest) (*services.TestReleaseResponse, error) { func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error {
res := &services.TestReleaseResponse{}
if !ValidName.MatchString(req.Name) { if !ValidName.MatchString(req.Name) {
return nil, errMissingRelease return errMissingRelease
} }
// finds the non-deleted release with the given name // finds the non-deleted release with the given name
r, err := s.env.Releases.Last(req.Name) rel, err := s.env.Releases.Last(req.Name)
if err != nil { if err != nil {
return nil, err return err
} }
tests, err := prepareTests(rel.Hooks, rel.Name)
kubeCli := s.env.KubeClient kubeCli := s.env.KubeClient
testSuite, err := runReleaseTestSuite(r.Hooks, kubeCli, r.Name, r.Namespace, req.Timeout)
testSuite, err := runReleaseTests(tests, rel, kubeCli, stream, req.Timeout)
if err != nil { if err != nil {
return nil, err return err
} }
r.TestSuite = testSuite rel.TestSuite = testSuite
res.Result = testSuite
return res, nil return nil
} }
...@@ -26,43 +26,55 @@ import ( ...@@ -26,43 +26,55 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services"
"k8s.io/helm/pkg/tiller/environment" "k8s.io/helm/pkg/tiller/environment"
"k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/timeconv"
) )
// change name to runReleaseTestSuite //TODO: testSuiteRunner.Run()
func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, name, namespace string, timeout int64) (*release.TestSuite, error) { //struct testSuiteRunner {
//suite *release.TestSuite,
//tests []string,
//kube environemtn.KubeClient,
//timeout int64
////stream or output channel
//}
suite := &release.TestSuite{} func runReleaseTests(tests []string, rel *release.Release, kube environment.KubeClient, stream services.ReleaseService_RunReleaseTestServer, timeout int64) (*release.TestSuite, error) {
suite.LastRun = timeconv.Now() results := []*release.TestRun{}
results := []*release.TestResult{}
tests, err := prepareTests(hooks, name) //TODO: add results to test suite
if err != nil { suite := &release.TestSuite{}
return suite, err suite.StartedAt = timeconv.Now()
}
for _, h := range tests { for _, h := range tests {
var sh simpleHead var sh simpleHead
err := yaml.Unmarshal([]byte(h), &sh) err := yaml.Unmarshal([]byte(h), &sh)
if err != nil { if err != nil {
//handle err better
return nil, err return nil, err
} }
ts := &release.TestResult{Name: sh.Metadata.Name}
// should this be lower? should we even be saving time to hook? if sh.Kind != "Pod" {
// TODO: should be start time really return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name)
ts.LastRun = timeconv.Now() }
ts := &release.TestRun{Name: sh.Metadata.Name}
ts.StartedAt = timeconv.Now()
if err := streamRunning(ts.Name, stream); err != nil {
return nil, err
}
resourceCreated := true resourceCreated := true
b := bytes.NewBufferString(h) b := bytes.NewBufferString(h)
if err := kube.Create(namespace, b); err != nil { if err := kube.Create(rel.Namespace, b); err != nil {
log.Printf("Could not create %s(%s): %v", ts.Name, sh.Kind, err)
ts.Info = err.Error()
//TODO: status option should be constant not random int
ts.Status = 2
resourceCreated = false resourceCreated = false
msg := fmt.Sprintf("ERROR: %s", err)
log.Printf(msg)
ts.Info = err.Error()
ts.Status = release.TestRun_FAILURE
if streamErr := streamMessage(msg, stream); streamErr != nil {
return nil, err
}
} }
status := api.PodUnknown status := api.PodUnknown
...@@ -70,31 +82,41 @@ func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, nam ...@@ -70,31 +82,41 @@ func runReleaseTestSuite(hooks []*release.Hook, kube environment.KubeClient, nam
if resourceCreated { if resourceCreated {
b.Reset() b.Reset()
b.WriteString(h) b.WriteString(h)
status, err = kube.WaitAndGetCompletedPodStatus(namespace, b, time.Duration(timeout)*time.Second) status, err = kube.WaitAndGetCompletedPodStatus(rel.Namespace, b, time.Duration(timeout)*time.Second)
if err != nil { if err != nil {
log.Printf("Error getting status for %s(%s): %s", ts.Name, sh.Kind, err)
ts.Info = err.Error()
ts.Status = 0
resourceCleanExit = false resourceCleanExit = false
log.Printf("Error getting status for pod %s: %s", ts.Name, err)
ts.Info = err.Error()
ts.Status = release.TestRun_UNKNOWN
if streamErr := streamFailed(ts.Name, stream); streamErr != nil {
return nil, err
}
} }
} }
// TODO: maybe better suited as a switch statement and include // TODO: maybe better suited as a switch statement and include
// PodUnknown, PodFailed, PodRunning, and PodPending scenarios // PodUnknown, PodFailed, PodRunning, and PodPending scenarios
if resourceCreated && resourceCleanExit && status == api.PodSucceeded { if resourceCreated && resourceCleanExit && status == api.PodSucceeded {
ts.Status = 1 ts.Status = release.TestRun_SUCCESS
if streamErr := streamSuccess(ts.Name, stream); streamErr != nil {
return nil, streamErr
}
} else if resourceCreated && resourceCleanExit && status == api.PodFailed { } else if resourceCreated && resourceCleanExit && status == api.PodFailed {
ts.Status = 2 ts.Status = release.TestRun_FAILURE
if streamErr := streamFailed(ts.Name, stream); streamErr != nil {
return nil, err
}
} }
results = append(results, ts) results = append(results, ts)
log.Printf("Test %s(%s) complete", ts.Name, sh.Kind) log.Printf("Test %s completed", ts.Name)
//TODO: recordTests() - add test results to configmap with standardized name //TODO: recordTests() - add test results to configmap with standardized name
} }
suite.Results = results suite.Results = results
log.Printf("Finished running test suite for %s", name) //TODO: delete flag
log.Printf("Finished running test suite for %s", rel.Name)
return suite, nil return suite, nil
} }
...@@ -145,3 +167,37 @@ func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) { ...@@ -145,3 +167,37 @@ func prepareTests(hooks []*release.Hook, releaseName string) ([]string, error) {
} }
return tests, nil return tests, nil
} }
func streamRunning(name string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := "RUNNING: " + name
if err := streamMessage(msg, stream); err != nil {
return err
}
return nil
}
func streamFailed(name string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s` for more info", name, name)
if err := streamMessage(msg, stream); err != nil {
return err
}
return nil
}
func streamSuccess(name string, stream services.ReleaseService_RunReleaseTestServer) error {
msg := fmt.Sprintf("PASSED: %s", name)
if err := streamMessage(msg, stream); err != nil {
return err
}
return nil
}
func streamMessage(msg string, stream services.ReleaseService_RunReleaseTestServer) error {
resp := &services.TestReleaseResponse{Msg: msg}
// TODO: handle err better
if err := stream.Send(resp); err != nil {
return err
}
return nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment