Merge pull request #120 from hasLeland/service-name-proposal

Implement service folder name change proposal #106
This commit is contained in:
Leland Batey 2016-12-20 15:18:08 -08:00 коммит произвёл GitHub
Родитель 56ab033558 3161f2d53d
Коммит c248479dbc
20 изменённых файлов: 315 добавлений и 242 удалений

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

@ -14,8 +14,8 @@ in a sane way, even for requests not made with the generated HTTP client.
The test harness works as follows:
- Runs truss against `transport/transport-test.proto`, which generates `transport/transport-service`
- Copy `transport/handlers` into `transport/transport-service`
- Runs truss against `transport/transport-test.proto`, which generates `transport/transportpermutations-service`
- Copy `transport/handlers` into `transport/transportpermutations-service`
- Run `go test -v`
- Runs truss again against `transport/transport-test.proto` (for regeneration tests)
- Run `go test -v`

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

@ -135,7 +135,7 @@ func truss(path string, options ...string) (string, error) {
return string(out), err
}
// buildTestService builds a truss service with the package TEST
// buildTestService builds a truss service with the package "test"
// into the `serviceDir`/bin directory
func buildTestService(serviceDir string) (err error) {
@ -156,14 +156,14 @@ func buildTestService(serviceDir string) (err error) {
return err
}
const serverPath = "/TEST-service/TEST-server"
const clientPath = "/TEST-service/TEST-cli-client"
const serverPath = "/test-service/test-server"
const clientPath = "/test-service/test-cli-client"
// Build server and client
errChan := make(chan error)
go goBuild("TEST-server", binDir, filepath.Join(relDir, serverPath), errChan)
go goBuild("TEST-cli-client", binDir, filepath.Join(relDir, clientPath), errChan)
go goBuild("test-server", binDir, filepath.Join(relDir, serverPath), errChan)
go goBuild("test-cli-client", binDir, filepath.Join(relDir, clientPath), errChan)
err = <-errChan
if err != nil {
@ -218,12 +218,12 @@ func goBuild(name, outputPath, relCodePath string, errChan chan error) {
errChan <- nil
}
// runServerAndClient execs a TEST-server and TEST-client and puts a
// runServerAndClient execs a test-server and test-client and puts a
// runReference to their interaction on the runRefs channel
func runServerAndClient(path string, port int, debugPort int) runReference {
// From within a folder with a truss `service`
// These are the paths to the compiled binaries
const relativeServerPath = "/bin/TEST-server"
const relativeServerPath = "/bin/test-server"
// Output buffer for the server Stdout and Stderr
serverOut := bytes.NewBuffer(nil)
@ -299,7 +299,7 @@ func runServerAndClient(path string, port int, debugPort int) runReference {
}
func runClient(path string, trans string, port int) ([]byte, bool) {
const relativeClientPath = "/bin/TEST-cli-client"
const relativeClientPath = "/bin/test-cli-client"
var client *exec.Cmd
switch trans {
@ -354,7 +354,7 @@ func cleanTests(servicesDir string) {
// removeTestFiles removes all files created by running truss and building the
// service from a single definition directory
func removeTestFiles(defDir string) {
os.RemoveAll(filepath.Join(defDir, "TEST-service"))
os.RemoveAll(filepath.Join(defDir, "test-service"))
os.RemoveAll(filepath.Join(defDir, "bin"))
os.RemoveAll(filepath.Join(defDir, "pbout"))
os.MkdirAll(filepath.Join(defDir, "pbout"), 0777)

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

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package basic;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Basic {
service TEST {
rpc GetBasic (BasicTypeRequest) returns (BasicTypeResponse) {
option (google.api.http) = {
get: "/1"

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

@ -1,12 +1,12 @@
syntax = "proto3";
package TEST;
package multifile;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
import "imported.proto";
service Basic {
service TEST {
rpc GetBasic (BasicTypeRequest) returns (BasicTypeResponse) {
option (google.api.http) = {
get: "/1"

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

@ -1,6 +1,6 @@
syntax = "proto3";
package TEST;
package multifile;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";

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

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package Repeated;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Repeated {
service TEST {
rpc GetRepeated (RepeatedTypeRequest) returns (RepeatedTypeResponse) {
option (google.api.http) = {
get: "/1"

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

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package nested;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Nested {
service TEST {
rpc GetNested (NestedTypeRequest) returns (NestedTypeResponse) {
option (google.api.http) = {
get: "/1"

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

@ -1,10 +1,10 @@
syntax = "proto3";
package TEST;
package map;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service Map {
service TEST {
rpc GetMap (MapTypeRequest) returns (MapTypeResponse) {
option (google.api.http) = {
get: "/1"

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

@ -14,7 +14,7 @@ all: test
test:
@echo -e '$(TRUSS_MSG)'
truss transport-test.proto
cp -r handlers transport-service
cp -r handlers transportpermutations-service
@echo -e '$(TEST_RUNNING_MSG)'
go test -v
@echo -e '$(TRUSS_AGAIN_MSG)'
@ -24,4 +24,4 @@ test:
$(MAKE) clean
clean:
rm -rf transport-service
rm -rf transportpermutations-service

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

@ -8,8 +8,8 @@ import (
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
grpcclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated/client/grpc"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
grpcclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated/client/grpc"
)
var grpcAddr string

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

@ -8,7 +8,7 @@ import (
"golang.org/x/net/context"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
)
// NewService returns a naïve, stateless implementation of Service.

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

@ -13,8 +13,8 @@ import (
// 3d Party
"golang.org/x/net/context"
// This Service
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
httpclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated/client/http"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
httpclient "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated/client/http"
"github.com/pkg/errors"
)

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

@ -14,9 +14,9 @@ import (
// Go Kit
"github.com/go-kit/kit/log"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service"
svc "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/generated"
handler "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transport-service/handlers/server"
pb "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service"
svc "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/generated"
handler "github.com/TuneLab/go-truss/cmd/_integration-tests/transport/transportpermutations-service/handlers/server"
)
func TestMain(m *testing.M) {

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

@ -14,7 +14,7 @@ import (
"github.com/TuneLab/go-truss/truss"
"github.com/TuneLab/go-truss/truss/execprotoc"
"github.com/TuneLab/go-truss/truss/parsepkgname"
"github.com/TuneLab/go-truss/truss/parsesvcname"
"github.com/TuneLab/go-truss/deftree"
"github.com/TuneLab/go-truss/gendoc"
@ -111,13 +111,10 @@ func parseInput() (*truss.Config, error) {
}
// Service Path
defFile, err := os.Open(cfg.DefPaths[0])
svcName, err := parsesvcname.FromPaths(cfg.GoPath, cfg.DefPaths)
svcName = strings.ToLower(svcName)
if err != nil {
return nil, errors.Wrapf(err, "cannot open package file %q", cfg.DefPaths[0])
}
svcName, err := parsepkgname.FromReader(defFile)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse package name from file %q", cfg.DefPaths[0])
return nil, errors.Wrap(err, "cannot parse service name from the provided definition files")
}
svcFolderName := svcName + "-service"

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

@ -7,8 +7,19 @@ import (
"strings"
"github.com/TuneLab/go-truss/deftree"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
func findServiceName(md *deftree.MicroserviceDefinition) string {
rv := "default"
for _, f := range md.Files {
for _, svc := range f.Services {
rv = svc.GetName()
}
}
return rv
}
// GenerateDocs accepts a deftree that represents an ast of a group of
// protofiles and returns map[string]io.Reader that represents a relative
// filestructure of generated docs
@ -23,7 +34,10 @@ func GenerateDocs(dt deftree.Deftree) map[string]io.Reader {
}
files := make(map[string]io.Reader)
files[dt.GetName()+"-service/docs/docs.md"] = strings.NewReader(response)
md := dt.(*deftree.MicroserviceDefinition)
// Normalize the service to prevent diversion from convention
svcname := strings.ToLower(generator.CamelCase(findServiceName(md)))
files[svcname+"-service/docs/docs.md"] = strings.NewReader(response)
return files
}

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

@ -6,6 +6,7 @@ import (
"go/format"
"io"
"io/ioutil"
"path"
"strings"
log "github.com/Sirupsen/logrus"
@ -30,8 +31,12 @@ func GenerateGokit(sd *svcdef.Svcdef, conf gengokit.Config) (map[string]io.Reade
codeGenFiles := make(map[string]io.Reader)
// Remove the suffix "-service" since it's added back in by templatePathToActual
svcname := strings.TrimSuffix(path.Base(conf.GoPackage), "-service")
for _, templPath := range templFiles.AssetNames() {
actualPath := templatePathToActual(templPath, sd.PkgName)
// Re-derive the actual path for this file based on the service output
// path provided by the truss main.go
actualPath := templatePathToActual(templPath, svcname)
file, err := generateResponseFile(templPath, data, conf.PreviousFiles[actualPath])
if err != nil {
return nil, errors.Wrap(err, "cannot render template")

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

@ -1,113 +0,0 @@
/*
Package parsepkgname provides functions for extracting the name of a package
from a protocol buffer definition file. For example, given a protocol buffer 3
file like this:
// A comment about this proto file
package examplepackage;
// and the rest of the file goes here
The functions in this package would extract the name "examplepackage" as the
name of the protobuf package.
*/
package parsepkgname
import (
"io"
"unicode"
"github.com/TuneLab/go-truss/deftree/svcparse"
)
type token int
const (
ident token = iota
whitespaceToken
comment
other
)
type Scanner interface {
// ReadUnit must return groups of runes representing at least the following
// lexical groups:
//
// ident
// comments (c++ style single line comments and block comments)
// whitespace
//
// If you need a scanner which provides these out of the box, see the
// SvcScanner struct in github.com/TuneLab/go-truss/deftree/svcparse
ReadUnit() ([]rune, error)
}
func categorize(unit []rune) token {
rv := other
r := unit[0]
switch {
case unicode.IsLetter(r):
rv = ident
case unicode.IsDigit(r):
rv = ident
case r == '_':
rv = ident
case unicode.IsSpace(r):
rv = whitespaceToken
case r == '/' && len(unit) > 1:
rv = comment
}
return rv
}
// FromReader accepts an io.Reader, the contents of which should be a
// valid proto3 file, and returns the name of the protobuf package which that
// file belongs to.
func FromReader(protofile io.Reader) (string, error) {
scanner := svcparse.NewSvcScanner(protofile)
return FromScanner(scanner)
}
// FromScanner accepts a Scanner for a protobuf file and returns the name of
// the protobuf package that the file belongs to.
func FromScanner(scanner Scanner) (string, error) {
foundpackage := false
// A nice way to ignore comments. Recursively calls itself until it
// recieves a unit from the scanner which is not a comment.
var readIgnoreComment func(Scanner) (token, []rune, error)
readIgnoreComment = func(scn Scanner) (token, []rune, error) {
unit, err := scn.ReadUnit()
if err != nil {
return other, nil, err
}
tkn := categorize(unit)
if tkn == comment {
return readIgnoreComment(scn)
}
return tkn, unit, err
}
// A tiny state machine to find two sequential idents: the ident "package"
// and the ident immediately following. That second ident will be the name
// of the package.
for {
tkn, unit, err := readIgnoreComment(scanner)
if err != nil {
return "", err
}
if foundpackage {
if tkn == ident {
return string(unit), nil
} else if tkn == whitespaceToken {
continue
} else {
foundpackage = false
}
} else {
if tkn == ident && string(unit) == "package" {
foundpackage = true
}
}
}
}

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

@ -1,88 +0,0 @@
package parsepkgname
import (
"io"
"strings"
"testing"
)
type testScanner struct {
contents [][]rune
position int
}
func (t *testScanner) ReadUnit() ([]rune, error) {
if t.position < len(t.contents) {
rv := t.contents[t.position]
t.position += 1
return rv, nil
}
return nil, io.EOF
}
func NewTestScanner(units []string) *testScanner {
rv := testScanner{position: 0}
for _, u := range units {
rv.contents = append(rv.contents, []rune(u))
}
return &rv
}
func TestFromScanner_simple(t *testing.T) {
basicContents := []string{
"\n",
"package",
" ",
"examplename",
";",
"\n",
}
scn := NewTestScanner(basicContents)
want := "examplename"
got, err := FromScanner(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestFromScanner_mid_comment(t *testing.T) {
contents := []string{
"\n",
"package",
" ",
"/* comment in the middle of the declaration */",
"examplename",
";",
"\n",
}
scn := NewTestScanner(contents)
want := "examplename"
got, err := FromScanner(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestFromReader(t *testing.T) {
code := `
// A comment about this proto file
package /* some mid-definition comment */ examplepackage;
// and the rest of the file goes here
`
name, err := FromReader(strings.NewReader(code))
if err != nil {
t.Fatal(err)
}
got := name
want := "examplepackage"
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}

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

@ -0,0 +1,89 @@
// Package parsesvcname will parse the service name of a protobuf package. The
// name returned will always be camelcased according to the conventions
// outlined in github.com/golang/protobuf/protoc-gen-go/generator.CamelCase.
package parsesvcname
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/TuneLab/go-truss/svcdef"
"github.com/TuneLab/go-truss/truss/execprotoc"
"github.com/pkg/errors"
)
// FromPaths accepts the paths of protobuf definition files and returns the
// name of the service in that protobuf definition file.
func FromPaths(gopath []string, protoDefPaths []string) (string, error) {
td, err := ioutil.TempDir("", "parsesvcname")
defer os.RemoveAll(td)
if err != nil {
return "", errors.Wrap(err, "failed to create temporary directory for .pb.go files")
}
err = execprotoc.GeneratePBDotGo(protoDefPaths, gopath, td)
if err != nil {
return "", errors.Wrap(err, "failed to generate .pb.go files from protobuf definition files")
}
// Get path names of .pb.go files
pbgoPaths := []string{}
for _, p := range protoDefPaths {
base := filepath.Base(p)
barename := strings.TrimSuffix(base, filepath.Ext(p))
pbgp := filepath.Join(td, barename+".pb.go")
pbgoPaths = append(pbgoPaths, pbgp)
}
// Open all .pb.go files and store in map to be passed to svcdef.New()
openFiles := func(paths []string) (map[string]io.Reader, error) {
rv := map[string]io.Reader{}
for _, p := range paths {
reader, err := os.Open(p)
if err != nil {
return nil, errors.Wrapf(err, "cannot open file %q", p)
}
rv[p] = reader
}
return rv, nil
}
pbgoFiles, err := openFiles(pbgoPaths)
if err != nil {
return "", errors.Wrap(err, "cannot open all .pb.go files")
}
pbFiles, err := openFiles(protoDefPaths)
if err != nil {
return "", errors.Wrap(err, "cannot open all .proto files")
}
sd, err := svcdef.New(pbgoFiles, pbFiles)
if err != nil {
return "", errors.Wrapf(err, "failed to create service definition; did you pass ALL the protobuf files to truss?")
}
return sd.Service.Name, nil
}
func FromReaders(gopath []string, protoDefReaders []io.Reader) (string, error) {
protoDir, err := ioutil.TempDir("", "parsesvcname-fromreaders")
if err != nil {
return "", errors.Wrap(err, "failed to create temporary directory for protobuf files")
}
// Ensures all the temporary files are removed
defer os.RemoveAll(protoDir)
protoDefPaths := []string{}
for _, rdr := range protoDefReaders {
f, err := ioutil.TempFile(protoDir, "parsesvcname-fromreader")
_, err = io.Copy(f, rdr)
if err != nil {
return "", errors.Wrap(err, "couldn't copy contents of our proto file into the os.File: ")
}
path := f.Name()
f.Close()
protoDefPaths = append(protoDefPaths, path)
}
return FromPaths(gopath, protoDefPaths)
}

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

@ -0,0 +1,169 @@
package parsesvcname
import (
"io"
"io/ioutil"
"os"
"strings"
"testing"
)
// Provide a basic proto file to test that FromPaths returns the name of the
// service in the file at the provided path.
func TestFromPaths(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
get: "/echo"
};
}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
protoDir, err := ioutil.TempDir("", "parsesvcname-test")
if err != nil {
t.Fatal("cannot create temp directory to store proto definition: ", err)
}
defer os.RemoveAll(protoDir)
f, err := ioutil.TempFile(protoDir, "trusstest")
_, err = io.Copy(f, strings.NewReader(protoStr))
if err != nil {
t.Fatal("couldn't copy contents of our proto file into the os.File: ", err)
}
path := f.Name()
f.Close()
svcname, err := FromPaths([]string{os.Getenv("GOPATH")}, []string{path})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Provide a basic protobuf file to FromReader to ensure it returns the service
// name we expect.
func TestFromReader(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
import "github.com/TuneLab/go-genproto/googleapis/api/serviceconfig/annotations.proto";
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {
option (google.api.http) = {
get: "/echo"
};
}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Ensure that passing a protobuf file that's not importing google annotations
// will function properly.
func TestNoAnnotations(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service BounceEcho {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "BounceEcho"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Test that having a service name which includes an underscore doesn't cause
// problems.
func TestUnderscoreService(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service foo_bar_test {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "FooBarTest"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}
// Test that having a service name which starts with an underscore doesn't
// cause problems.
func TestLeadingUnderscoreService(t *testing.T) {
protoStr := `
syntax = "proto3";
package echo;
service _Foo_Bar {
rpc Echo (EchoRequest) returns (EchoResponse) {}
}
message EchoRequest {
string In = 1;
}
message EchoResponse {
string Out = 1;
}
`
svcname, err := FromReaders([]string{os.Getenv("GOPATH")}, []io.Reader{strings.NewReader(protoStr)})
if err != nil {
t.Fatal("failed to get service name from path: ", err)
}
if got, want := svcname, "XFoo_Bar"; got != want {
t.Fatalf("got != want; got = %q, want = %q", got, want)
}
}