bootcfg: Add bootcfg gRPC client and server packages

* Add standalone gRPC binary example
This commit is contained in:
Dalton Hubble
2016-02-29 13:28:32 -08:00
parent 091fba9354
commit 798be8bdb8
15 changed files with 633 additions and 7 deletions

52
bootcfg/client/client.go Normal file
View File

@@ -0,0 +1,52 @@
package client
import (
"google.golang.org/grpc"
pb "github.com/coreos/coreos-baremetal/bootcfg/server/serverpb"
)
// Config configures a Client.
type Config struct {
// List of endpoint URLs
Endpoints []string
}
// Client provides a bootcfg client RPC session.
type Client struct {
Groups pb.GroupsClient
conn *grpc.ClientConn
}
// New creates a new Client from the given Config.
func New(config *Config) (*Client, error) {
return newClient(config)
}
func newClient(config *Config) (*Client, error) {
conn, err := retryDialer(config)
if err != nil {
return nil, err
}
client := &Client{
Groups: pb.NewGroupsClient(conn),
conn: conn,
}
return client, nil
}
// retryDialer attemps to Dial each endpoint until a client connection
// is established.
func retryDialer(config *Config) (*grpc.ClientConn, error) {
opts := []grpc.DialOption{
grpc.WithInsecure(),
}
var err error
for _, endp := range config.Endpoints {
conn, err := grpc.Dial(endp, opts...)
if err == nil {
return conn, nil
}
}
return nil, err
}

41
bootcfg/server/server.go Normal file
View File

@@ -0,0 +1,41 @@
package server
import (
"golang.org/x/net/context"
"github.com/coreos/coreos-baremetal/bootcfg/storage"
pb "github.com/coreos/coreos-baremetal/bootcfg/server/serverpb"
)
// Config configures an RPC Server.
type Config struct {
Store storage.Store
}
// server implements the grpc GroupsServer interface.
type server struct {
store storage.Store
}
// NewServer returns a new server.
func NewServer(config *Config) pb.GroupsServer {
return &server{
store: config.Store,
}
}
func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
group, err := s.store.GetGroup(req.Id)
if err != nil {
return nil, err
}
return &pb.GetResponse{Group: group}, nil
}
func (s *server) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
groups, err := s.store.ListGroups()
if err != nil {
return nil, err
}
return &pb.ListResponse{Groups: groups}, nil
}

View File

@@ -0,0 +1,2 @@
// Package serverpb provides bootcfg protobuf client and server interfaces.
package serverpb

View File

@@ -0,0 +1,204 @@
// Code generated by protoc-gen-go.
// source: server.proto
// DO NOT EDIT!
/*
Package serverpb is a generated protocol buffer package.
It is generated from these files:
server.proto
It has these top-level messages:
GetRequest
ListRequest
GetResponse
ListResponse
*/
package serverpb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import storagepb "github.com/coreos/coreos-baremetal/bootcfg/storage/storagepb"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
const _ = proto.ProtoPackageIsVersion1
type GetRequest struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
}
func (m *GetRequest) Reset() { *m = GetRequest{} }
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
func (*GetRequest) ProtoMessage() {}
func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
type ListRequest struct {
}
func (m *ListRequest) Reset() { *m = ListRequest{} }
func (m *ListRequest) String() string { return proto.CompactTextString(m) }
func (*ListRequest) ProtoMessage() {}
func (*ListRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
type GetResponse struct {
Group *storagepb.Group `protobuf:"bytes,1,opt,name=group" json:"group,omitempty"`
}
func (m *GetResponse) Reset() { *m = GetResponse{} }
func (m *GetResponse) String() string { return proto.CompactTextString(m) }
func (*GetResponse) ProtoMessage() {}
func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *GetResponse) GetGroup() *storagepb.Group {
if m != nil {
return m.Group
}
return nil
}
type ListResponse struct {
Groups []*storagepb.Group `protobuf:"bytes,1,rep,name=groups" json:"groups,omitempty"`
}
func (m *ListResponse) Reset() { *m = ListResponse{} }
func (m *ListResponse) String() string { return proto.CompactTextString(m) }
func (*ListResponse) ProtoMessage() {}
func (*ListResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *ListResponse) GetGroups() []*storagepb.Group {
if m != nil {
return m.Groups
}
return nil
}
func init() {
proto.RegisterType((*GetRequest)(nil), "serverpb.GetRequest")
proto.RegisterType((*ListRequest)(nil), "serverpb.ListRequest")
proto.RegisterType((*GetResponse)(nil), "serverpb.GetResponse")
proto.RegisterType((*ListResponse)(nil), "serverpb.ListResponse")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// Client API for Groups service
type GroupsClient interface {
// Get a machine Group by id.
Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error)
// List all machine Groups.
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
}
type groupsClient struct {
cc *grpc.ClientConn
}
func NewGroupsClient(cc *grpc.ClientConn) GroupsClient {
return &groupsClient{cc}
}
func (c *groupsClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) {
out := new(GetResponse)
err := grpc.Invoke(ctx, "/serverpb.Groups/Get", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *groupsClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) {
out := new(ListResponse)
err := grpc.Invoke(ctx, "/serverpb.Groups/List", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Groups service
type GroupsServer interface {
// Get a machine Group by id.
Get(context.Context, *GetRequest) (*GetResponse, error)
// List all machine Groups.
List(context.Context, *ListRequest) (*ListResponse, error)
}
func RegisterGroupsServer(s *grpc.Server, srv GroupsServer) {
s.RegisterService(&_Groups_serviceDesc, srv)
}
func _Groups_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(GetRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(GroupsServer).Get(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
func _Groups_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
in := new(ListRequest)
if err := dec(in); err != nil {
return nil, err
}
out, err := srv.(GroupsServer).List(ctx, in)
if err != nil {
return nil, err
}
return out, nil
}
var _Groups_serviceDesc = grpc.ServiceDesc{
ServiceName: "serverpb.Groups",
HandlerType: (*GroupsServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Get",
Handler: _Groups_Get_Handler,
},
{
MethodName: "List",
Handler: _Groups_List_Handler,
},
},
Streams: []grpc.StreamDesc{},
}
var fileDescriptor0 = []byte{
// 241 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x50, 0xc1, 0x4a, 0x03, 0x31,
0x10, 0xb5, 0x56, 0x17, 0x9d, 0xad, 0x22, 0x41, 0x45, 0x16, 0x0f, 0x92, 0x83, 0xf4, 0x62, 0x02,
0x55, 0xd1, 0x3f, 0x28, 0x82, 0xa7, 0xfd, 0x83, 0xcd, 0x3a, 0xc6, 0x05, 0xeb, 0xc4, 0x4c, 0x56,
0x7f, 0xdf, 0x6d, 0xb2, 0xd1, 0x52, 0x3c, 0x4d, 0xf2, 0xde, 0xbc, 0xf7, 0x66, 0x06, 0x66, 0x8c,
0xfe, 0x0b, 0xbd, 0x72, 0x9e, 0x02, 0x89, 0x83, 0xf4, 0x73, 0xa6, 0x7a, 0xb2, 0x5d, 0x78, 0xeb,
0x8d, 0x6a, 0x69, 0xa5, 0x5b, 0xf2, 0x48, 0x3c, 0x96, 0x1b, 0xd3, 0x78, 0x5c, 0x61, 0x68, 0xde,
0xb5, 0x21, 0x0a, 0xed, 0xab, 0xd5, 0x1c, 0xc8, 0x37, 0x16, 0x73, 0x75, 0x46, 0x5b, 0x4f, 0xbd,
0xe3, 0x64, 0x2a, 0x2f, 0x01, 0x96, 0x18, 0x6a, 0xfc, 0xec, 0x91, 0x83, 0x38, 0x86, 0xdd, 0xee,
0xe5, 0x62, 0x72, 0x35, 0x99, 0x1f, 0xd6, 0xc3, 0x4b, 0x1e, 0x41, 0xf9, 0xdc, 0x71, 0xa6, 0xe5,
0x3d, 0x94, 0xb1, 0x99, 0x1d, 0x7d, 0x30, 0x8a, 0x6b, 0xd8, 0x8f, 0x5e, 0x51, 0x50, 0x2e, 0x4e,
0xd4, 0x6f, 0x86, 0x5a, 0xae, 0xf1, 0x3a, 0xd1, 0xf2, 0x11, 0x66, 0xc9, 0x65, 0xd4, 0xcd, 0xa1,
0x48, 0x33, 0x0c, 0xc2, 0xe9, 0xbf, 0xc2, 0x91, 0x5f, 0x7c, 0x43, 0x11, 0x01, 0x16, 0x77, 0x30,
0x1d, 0xa2, 0xc5, 0xa9, 0xca, 0x47, 0x50, 0x7f, 0x63, 0x57, 0x67, 0x5b, 0x68, 0xca, 0x91, 0x3b,
0xe2, 0x01, 0xf6, 0xd6, 0xc9, 0x62, 0xa3, 0x61, 0x63, 0x9f, 0xea, 0x7c, 0x1b, 0xce, 0x42, 0x53,
0xc4, 0xeb, 0xdc, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x46, 0xb8, 0x4d, 0x82, 0x01, 0x00,
0x00,
}

View File

@@ -0,0 +1,27 @@
syntax = "proto3";
package serverpb;
import "github.com/coreos/coreos-baremetal/bootcfg/storage/storagepb/groups.proto";
service Groups {
// Get a machine Group by id.
rpc Get(GetRequest) returns (GetResponse) {}
// List all machine Groups.
rpc List(ListRequest) returns (ListResponse) {}
}
message GetRequest {
string id = 1;
}
message ListRequest {
}
message GetResponse {
storagepb.Group group = 1;
}
message ListResponse {
repeated storagepb.Group groups = 1;
}

View File

@@ -0,0 +1,61 @@
package storage
import (
"errors"
"github.com/coreos/coreos-baremetal/bootcfg/storage/storagepb"
)
// Errors querying a Store.
var (
ErrGroupNotFound = errors.New("storage: No Group found")
)
// A Store provides machine Groups.
type Store interface {
// Get a machine Group by id.
GetGroup(id string) (*storagepb.Group, error)
// List all machine Groups.
ListGroups() ([]*storagepb.Group, error)
}
// Config initializes a memStore.
type Config struct {
Groups []*storagepb.Group
}
// memStore implements ths Store interface.
type memStore struct {
groups map[string] *storagepb.Group
}
// NewMemStore returns a new memory-backed Store.
func NewMemStore(config *Config) Store {
groups := make(map[string]*storagepb.Group)
for _, group := range config.Groups {
groups[group.Id] = group
}
return &memStore{
groups: groups,
}
}
// GetGroup returns a machine Group by id.
func (s *memStore) GetGroup(id string) (*storagepb.Group, error) {
val, ok := s.groups[id]
if !ok {
return nil, ErrGroupNotFound
}
return val, nil
}
// ListGroups lists all machine Groups.
func (s *memStore) ListGroups() ([]*storagepb.Group, error) {
groups := make([]*storagepb.Group, len(s.groups))
i := 0
for _, g := range s.groups {
groups[i] = g
i++
}
return groups, nil
}

View File

@@ -0,0 +1,2 @@
// Package storagepb provides storage protobuf client and server interfaces.
package storagepb

View File

@@ -0,0 +1,81 @@
// Code generated by protoc-gen-go.
// source: groups.proto
// DO NOT EDIT!
/*
Package storagepb is a generated protocol buffer package.
It is generated from these files:
groups.proto
It has these top-level messages:
Group
*/
package storagepb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
const _ = proto.ProtoPackageIsVersion1
type Group struct {
// machine readable Id
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
// human readable name
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
// profile id
Profile string `protobuf:"bytes,3,opt,name=profile" json:"profile,omitempty"`
// tags required to match the group
Requirements map[string]string `protobuf:"bytes,4,rep,name=requirements" json:"requirements,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// custom metadata
Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}
func (m *Group) Reset() { *m = Group{} }
func (m *Group) String() string { return proto.CompactTextString(m) }
func (*Group) ProtoMessage() {}
func (*Group) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Group) GetRequirements() map[string]string {
if m != nil {
return m.Requirements
}
return nil
}
func (m *Group) GetMetadata() map[string]string {
if m != nil {
return m.Metadata
}
return nil
}
func init() {
proto.RegisterType((*Group)(nil), "storagepb.Group")
}
var fileDescriptor0 = []byte{
// 217 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2f, 0xca, 0x2f,
0x2d, 0x28, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x2e, 0xc9, 0x2f, 0x4a, 0x4c,
0x4f, 0x2d, 0x48, 0x52, 0x3a, 0xce, 0xc4, 0xc5, 0xea, 0x0e, 0x92, 0x13, 0xe2, 0xe3, 0x62, 0xca,
0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb2, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12,
0x73, 0x53, 0x25, 0x98, 0xc0, 0x22, 0x60, 0xb6, 0x90, 0x04, 0x17, 0x3b, 0xd0, 0x84, 0xb4, 0xcc,
0x9c, 0x54, 0x09, 0x66, 0xb0, 0x30, 0x8c, 0x2b, 0xe4, 0xc6, 0xc5, 0x53, 0x94, 0x5a, 0x58, 0x9a,
0x59, 0x94, 0x9a, 0x9b, 0x9a, 0x57, 0x52, 0x2c, 0xc1, 0xa2, 0xc0, 0xac, 0xc1, 0x6d, 0xa4, 0xa4,
0x07, 0xb7, 0x49, 0x0f, 0x6c, 0x8b, 0x5e, 0x10, 0x92, 0x22, 0xd7, 0xbc, 0x92, 0xa2, 0xca, 0x20,
0x14, 0x7d, 0x42, 0x56, 0x5c, 0x1c, 0xb9, 0xa9, 0x25, 0x89, 0x29, 0x89, 0x25, 0x89, 0x12, 0xac,
0x60, 0x33, 0xe4, 0x30, 0xcc, 0xf0, 0x85, 0x2a, 0x80, 0xe8, 0x87, 0xab, 0x97, 0xb2, 0xe7, 0x12,
0xc4, 0x30, 0x5e, 0x48, 0x80, 0x8b, 0x39, 0x3b, 0xb5, 0x12, 0xea, 0x2f, 0x10, 0x53, 0x48, 0x84,
0x8b, 0xb5, 0x2c, 0x31, 0xa7, 0x14, 0xe6, 0x33, 0x08, 0xc7, 0x8a, 0xc9, 0x82, 0x51, 0xca, 0x9a,
0x8b, 0x17, 0xc5, 0x6c, 0x52, 0x34, 0x27, 0xb1, 0x81, 0xc3, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff,
0xff, 0xcc, 0x89, 0x49, 0x42, 0x6b, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package storagepb;
message Group {
// machine readable Id
string id = 1;
// human readable name
string name = 2;
// profile id
string profile = 3;
// tags required to match the group
map<string, string> requirements = 4;
// custom metadata
map<string, string> metadata = 5;
}

3
build
View File

@@ -2,3 +2,6 @@
LD_FLAGS="-w -X main.version=$(./git-version)"
CGO_ENABLED=0 go build -o bin/bootcfg -ldflags "$LD_FLAGS" -a -tags netgo github.com/coreos/coreos-baremetal/cmd/bootcfg
# gRPC server as a separate binary
CGO_ENABLED=0 go build -o bin/bootcfg-rpc -ldflags "$LD_FLAGS" -a -tags netgo github.com/coreos/coreos-baremetal/cmd/bootcfg-rpc

85
cmd/bootcfg-rpc/main.go Normal file
View File

@@ -0,0 +1,85 @@
package main
import (
"flag"
"fmt"
"net"
"net/url"
"os"
"github.com/coreos/pkg/capnslog"
"github.com/coreos/pkg/flagutil"
"google.golang.org/grpc"
bootcfg "github.com/coreos/coreos-baremetal/bootcfg/server"
pb "github.com/coreos/coreos-baremetal/bootcfg/server/serverpb"
"github.com/coreos/coreos-baremetal/bootcfg/storage"
"github.com/coreos/coreos-baremetal/config"
)
var (
// version provided by compile time flag: -ldflags "-X main.version $GIT_SHA"
version = "was not built properly"
log = capnslog.NewPackageLogger("github.com/coreos/coreos-baremetal/cmd/bootcfg-rpc", "main")
)
func main() {
flags := struct {
address string
configPath string
version bool
help bool
}{}
flag.StringVar(&flags.address, "address", "127.0.0.1:8081", "gRPC listen address")
flag.StringVar(&flags.configPath, "config", "./data/config.yaml", "Path to config file")
// subcommands
flag.BoolVar(&flags.version, "version", false, "print version and exit")
flag.BoolVar(&flags.help, "help", false, "print usage and exit")
// parse command-line and environment variable arguments
flag.Parse()
if err := flagutil.SetFlagsFromEnv(flag.CommandLine, "BOOTCFG"); err != nil {
log.Fatal(err.Error())
}
if flags.version {
fmt.Println(version)
return
}
if flags.help {
flag.Usage()
return
}
// validate arguments
if url, err := url.Parse(flags.address); err != nil || url.String() == "" {
log.Fatal("A valid HTTP listen address is required")
}
if finfo, err := os.Stat(flags.configPath); err != nil || finfo.IsDir() {
log.Fatal("A path to a config file is required")
}
// load bootstrap config
cfg, err := config.LoadConfig(flags.configPath)
if err != nil {
log.Fatal(err)
}
// storage
store := storage.NewMemStore(&storage.Config{
Groups: cfg.PBGroups(),
})
// gRPC Server
log.Infof("starting bootcfg gRPC server on %s", flags.address)
lis, err := net.Listen("tcp", flags.address)
if err != nil {
log.Fatalf("failed to start listening: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterGroupsServer(grpcServer, bootcfg.NewServer(&bootcfg.Config{
Store: store,
}))
grpcServer.Serve(lis)
}

View File

@@ -8,11 +8,12 @@ import (
"os"
"strings"
"github.com/coreos/pkg/capnslog"
"github.com/coreos/pkg/flagutil"
"github.com/coreos/coreos-baremetal/api"
"github.com/coreos/coreos-baremetal/config"
"github.com/coreos/coreos-baremetal/sign"
"github.com/coreos/pkg/capnslog"
"github.com/coreos/pkg/flagutil"
)
var (
@@ -104,7 +105,7 @@ func main() {
}
store.BootstrapGroups(cfg.Groups)
// API server
// HTTP server
config := &api.Config{
Store: store,
AssetsPath: flags.assetsPath,
@@ -112,9 +113,9 @@ func main() {
ArmoredSigner: armoredSigner,
}
server := api.NewServer(config)
log.Infof("starting config server on %s", flags.address)
log.Infof("starting bootcfg HTTP server on %s", flags.address)
err = http.ListenAndServe(flags.address, server.HTTPHandler())
if err != nil {
log.Fatalf("failed to start listening: %s", err)
log.Fatalf("failed to start listening: %v", err)
}
}

View File

@@ -9,6 +9,8 @@ import (
"strings"
"github.com/coreos/coreos-baremetal/api"
"github.com/coreos/coreos-baremetal/bootcfg/storage/storagepb"
"github.com/satori/go.uuid"
"gopkg.in/yaml.v2"
)
@@ -71,3 +73,31 @@ func (c *Config) validate() error {
}
return nil
}
// PBGroups returns the parsed storagepb.Group slice.
func (c *Config) PBGroups() []*storagepb.Group {
groups := make([]*storagepb.Group, len(c.Groups))
i := 0
for _, g := range c.Groups {
group := &storagepb.Group{
Id: uuid.NewV4().String(),
Name: g.Name,
Profile: g.Spec,
Metadata: make(map[string]string),
Requirements: g.Matcher,
}
// gRPC message fields must have concrete types.
// Limit YAML metadata nesting to a depth of 1 for now.
for key, unknown := range g.Metadata {
switch val := unknown.(type) {
case string:
group.Metadata[key] = val
default:
// skip subtree
}
}
groups[i] = group
i++
}
return groups
}

19
scripts/codegen.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Generate Go protobuf code.
set -e
COREOS_ROOT="$GOPATH/src/"
# protobuf subpackages end in "pb"
PBUFS=$(go list ./... | grep -v /vendor | grep 'pb$')
# change into each protobuf directory
for pkg in $PBUFS ; do
abs_path=${GOPATH}/src/${pkg}
echo Generating $abs_path
pushd ${abs_path} > /dev/null
# generate protocol buffers, make other .proto files available to import
protoc --go_out=plugins=grpc:. -I=.:"${COREOS_ROOT}" *.proto
popd > /dev/null
done

7
test
View File

@@ -3,7 +3,10 @@
PKGS=$(go list ./... | grep -v /vendor)
FORMATTABLE="./api ./config ./cmd ./sign"
go test $PKGS
LINT_EXCLUDE='(/vendor|pb$)'
LINTABLE=$(go list ./... | grep -v -E $LINT_EXCLUDE)
go test $PKGS -cover
go vet $PKGS
echo "Checking gofmt..."
@@ -14,7 +17,7 @@ if [ -n "${fmtRes}" ]; then
fi
echo "Checking golint..."
lintRes=$(echo $PKGS | xargs -n 1 golint)
lintRes=$(echo $LINTABLE | xargs -n 1 golint)
if [ -n "${lintRes}" ]; then
echo -e "golint checking failed:\n${lintRes}"
exit 2