diff --git a/Dockerfile b/Dockerfile index 23127a7e8..43089614d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,6 +58,6 @@ COPY --from=builder /usr/local/bin /usr/local/bin COPY --from=builder /usr/local/include /usr/local/include COPY --from=builder /go/src/github.com/grpc-ecosystem/grpc-gateway ${GOPATH}/src/github.com/grpc-ecosystem/grpc-gateway WORKDIR /project -RUN apk add --update --no-cache make bash curl git protobuf=${PROTOBUF_VERSION}-${ALPINE_PROTOBUF_VERSION_SUFFIX} && \ +RUN apk add --update --no-cache make protobuf=${PROTOBUF_VERSION}-${ALPINE_PROTOBUF_VERSION_SUFFIX} && \ rm -rf /var/cache/apk/* ENTRYPOINT ["sprout"] diff --git a/cmd/generate.go b/cmd/generate.go index 9281dcf93..1a0059905 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -4,6 +4,10 @@ import ( "github.com/commitdev/sprout/config" "github.com/commitdev/sprout/generate/golang" "github.com/commitdev/sprout/generate/proto" + "github.com/commitdev/sprout/generate/docker" + "github.com/commitdev/sprout/generate/http" + + "log" "github.com/spf13/cobra" @@ -42,7 +46,13 @@ var generateCmd = &cobra.Command{ switch language { case Go: golang.Generate(Templator, cfg) + docker.GenerateGoAppDockerFile(Templator, cfg) + + } + if cfg.Network.Http.Enabled { + http.GenerateHttpGW(Templator, cfg) + docker.GenerateGoHttpGWDockerFile(Templator, cfg) } }, } diff --git a/example/hello-world/.dockerignore b/example/hello-world/.dockerignore new file mode 100644 index 000000000..e69de29bb diff --git a/example/hello-world/docker/app/Dockerfile b/example/hello-world/docker/app/Dockerfile new file mode 100644 index 000000000..c9cbdba6e --- /dev/null +++ b/example/hello-world/docker/app/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.12.6@sha256:83e8267be041b3ddf6a5792c7e464528408f75c446745642db08cfe4e8d58d18 AS build +WORKDIR /cache +COPY go.mod . +COPY . . +RUN go build -o hello-world + +FROM alpine +RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 +RUN apk update && apk add ca-certificates +COPY --from=build /cache/hello-world /app/ +ENTRYPOINT /app/hello-world diff --git a/example/hello-world/docker/http/Dockerfile b/example/hello-world/docker/http/Dockerfile new file mode 100644 index 000000000..af5b4716e --- /dev/null +++ b/example/hello-world/docker/http/Dockerfile @@ -0,0 +1,10 @@ +FROM golang:1.12.6@sha256:83e8267be041b3ddf6a5792c7e464528408f75c446745642db08cfe4e8d58d18 AS build +WORKDIR /cache +COPY . . +RUN go build http/main.go -o hello-world-http + +FROM alpine +RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 +RUN apk update && apk add ca-certificates +COPY --from=build /cache/hello-world /app/ +ENTRYPOINT /app/hello-world-http diff --git a/example/hello-world/hello-world-idl/Makefile b/example/hello-world/hello-world-idl/Makefile index 20e02c511..33530a38e 100644 --- a/example/hello-world/hello-world-idl/Makefile +++ b/example/hello-world/hello-world-idl/Makefile @@ -42,9 +42,9 @@ generate-web: cp -f -rv proto/Proto/* gen/web rm -rf proto/proto proto/Proto generate-http: - mkdir -p gen/http + mkdir -p gen/go protoc ${PROTO_SOURCES} --grpc-gateway_out=logtostderr=true,paths=source_relative:proto --swagger_out=logtostderr=true:proto ./proto/health/*.proto protoc ${PROTO_SOURCES} --grpc-gateway_out=logtostderr=true,paths=source_relative:proto --swagger_out=logtostderr=true:proto ./proto/helloworld/*.proto - cp -f -rv proto/proto/* gen/http + cp -f -rv proto/proto/* gen/go rm -rf proto/proto diff --git a/example/hello-world/hello-world-idl/gen/http/health/health.pb.gw.go b/example/hello-world/hello-world-idl/gen/go/health/health.pb.gw.go similarity index 100% rename from example/hello-world/hello-world-idl/gen/http/health/health.pb.gw.go rename to example/hello-world/hello-world-idl/gen/go/health/health.pb.gw.go diff --git a/example/hello-world/hello-world-idl/gen/http/health/health.swagger.json b/example/hello-world/hello-world-idl/gen/go/health/health.swagger.json similarity index 100% rename from example/hello-world/hello-world-idl/gen/http/health/health.swagger.json rename to example/hello-world/hello-world-idl/gen/go/health/health.swagger.json diff --git a/example/hello-world/hello-world-idl/gen/http/helloworld/helloworld.pb.gw.go b/example/hello-world/hello-world-idl/gen/go/helloworld/helloworld.pb.gw.go similarity index 100% rename from example/hello-world/hello-world-idl/gen/http/helloworld/helloworld.pb.gw.go rename to example/hello-world/hello-world-idl/gen/go/helloworld/helloworld.pb.gw.go diff --git a/example/hello-world/hello-world-idl/gen/http/helloworld/helloworld.swagger.json b/example/hello-world/hello-world-idl/gen/go/helloworld/helloworld.swagger.json similarity index 100% rename from example/hello-world/hello-world-idl/gen/http/helloworld/helloworld.swagger.json rename to example/hello-world/hello-world-idl/gen/go/helloworld/helloworld.swagger.json diff --git a/example/hello-world/http/main.go b/example/hello-world/http/main.go new file mode 100644 index 000000000..903cb39ed --- /dev/null +++ b/example/hello-world/http/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "log" + "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + health "github.com/yourrepo/hello-world-idl/gen/go/health" + helloworld "github.com/yourrepo/hello-world-idl/gen/go/helloworld" +) + +func run(endpoint string, listening string) error { + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithInsecure()} + err := health.RegisterHealthHandlerFromEndpoint(ctx, mux, endpoint, opts) + err = helloworld.RegisterHelloworldHandlerFromEndpoint(ctx, mux, endpoint, opts) + + if err != nil { + return err + } + + return http.ListenAndServe(listening, mux) +} + +func main() { + endpoint := "0.0.0.0:3000" + listening := "0.0.0.0:8080" + log.Printf("Starting http grpc gateway server on %v...", listening) + + if err := run(endpoint, listening); err != nil { + log.Fatal(err) + } +} diff --git a/generate/docker/generate.go b/generate/docker/generate.go new file mode 100644 index 000000000..35ef57a04 --- /dev/null +++ b/generate/docker/generate.go @@ -0,0 +1,18 @@ +package docker + +import ( + "github.com/commitdev/sprout/util" + + "github.com/commitdev/sprout/config" + "github.com/commitdev/sprout/templator" +) + +func GenerateGoAppDockerFile(templator *templator.Templator, config *config.SproutConfig) { + util.TemplateFileIfDoesNotExist("docker/app", "Dockerfile", templator.Docker.ApplicationDocker, config) + util.TemplateFileIfDoesNotExist("./", ".dockerignore", templator.Docker.DockerIgnore, config) + +} + +func GenerateGoHttpGWDockerFile(templator *templator.Templator, config *config.SproutConfig) { + util.TemplateFileIfDoesNotExist("docker/http", "Dockerfile", templator.Docker.HttpGatewayDocker, config) +} diff --git a/generate/http/generate.go b/generate/http/generate.go new file mode 100644 index 000000000..0a0c34e0e --- /dev/null +++ b/generate/http/generate.go @@ -0,0 +1,12 @@ +package http + +import ( + "github.com/commitdev/sprout/util" + + "github.com/commitdev/sprout/config" + "github.com/commitdev/sprout/templator" +) + +func GenerateHttpGW(templator *templator.Templator, config *config.SproutConfig) { + util.TemplateFileIfDoesNotExist("http", "main.go", templator.Go.GoHttpGW, config) +} diff --git a/generate/proto/generate.go b/generate/proto/generate.go index 89ae2817b..a2426ae23 100644 --- a/generate/proto/generate.go +++ b/generate/proto/generate.go @@ -1,12 +1,12 @@ package proto import ( - "fmt" "bytes" + "fmt" - "github.com/commitdev/sprout/util" "github.com/commitdev/sprout/config" "github.com/commitdev/sprout/templator" + "github.com/commitdev/sprout/util" "log" "os" "os/exec" @@ -80,10 +80,10 @@ func GenerateServiceProtobufFiles(templator *templator.Templator, cfg *config.Sp f, err := os.Create(serviceProtoFilePath) - data:= struct { + data := struct { *config.SproutConfig ServiceName string - } { + }{ cfg, s.Name, } diff --git a/go.sum b/go.sum index b67ea5014..80d74ab6c 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/commitdev/sprout/example/hello-world v0.0.0-20190827182108-cfad4c3d94cc h1:zBLjQxkC2LT1e9eMs0I2k26NXXZVT/XGrnuqdAsFt3A= github.com/commitdev/sprout/example/hello-world v0.0.0-20190827183525-9eeb1651f4f4 h1:pQw2XR8va971sW7QIX18I7FWft54TjLdQex5MHCcVtg= github.com/commitdev/sprout/example/hello-world v0.0.0-20191006174419-73168768419f h1:zfmpI6udrSYU1Ly4YYqEvE3WyoGrgzqLgevS6FHWlsw= +github.com/commitdev/sprout/example/hello-world v0.0.0-20191007000403-b09452cbef06 h1:L6MAZtnpxfjj15iW3zllF3tVNjXaUB1qsNjMGISYRVI= github.com/commitdev/sprout/example/hello-world-idl v0.0.0-20190910021125-8ac64211bb19 h1:ULmWBQ848cHxodGxtQWas0lWPAh/ZntpV1QsobbasMA= github.com/commitdev/sprout/example/hello-world/hello-world-idl v0.0.0-20191006174419-73168768419f h1:yT3SIhGDFr+Pf7uW0wllWwp+HFeRvObO2s++6upn8g4= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= diff --git a/templates/docker/dockerfile_app.tmpl b/templates/docker/dockerfile_app.tmpl new file mode 100644 index 000000000..952d5baf7 --- /dev/null +++ b/templates/docker/dockerfile_app.tmpl @@ -0,0 +1,11 @@ +FROM golang:1.12.6@sha256:83e8267be041b3ddf6a5792c7e464528408f75c446745642db08cfe4e8d58d18 AS build +WORKDIR /cache +COPY go.mod . +COPY . . +RUN go build -o {{ .Name }} + +FROM alpine +RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 +RUN apk update && apk add ca-certificates +COPY --from=build /cache/{{ .Name }} /app/ +ENTRYPOINT /app/{{ .Name }} diff --git a/templates/docker/dockerfile_http.tmpl b/templates/docker/dockerfile_http.tmpl new file mode 100644 index 000000000..36c7ae162 --- /dev/null +++ b/templates/docker/dockerfile_http.tmpl @@ -0,0 +1,10 @@ +FROM golang:1.12.6@sha256:83e8267be041b3ddf6a5792c7e464528408f75c446745642db08cfe4e8d58d18 AS build +WORKDIR /cache +COPY . . +RUN go build http/main.go -o {{ .Name }}-http + +FROM alpine +RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 +RUN apk update && apk add ca-certificates +COPY --from=build /cache/{{ .Name }} /app/ +ENTRYPOINT /app/{{ .Name }}-http diff --git a/templates/docker/dockerignore.tmpl b/templates/docker/dockerignore.tmpl new file mode 100644 index 000000000..e69de29bb diff --git a/templates/golang/http_gw.tmpl b/templates/golang/http_gw.tmpl new file mode 100644 index 000000000..c2c36403c --- /dev/null +++ b/templates/golang/http_gw.tmpl @@ -0,0 +1,45 @@ +package main + +import ( + "log" + "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + health "{{ $.GitRepo }}/{{ $.Name }}-idl/gen/go/health" + {{- range .Services}} + {{ .Name }} "{{ $.GitRepo }}/{{ $.Name }}-idl/gen/go/{{ .Name }}" + {{- end}} +) + +func run(endpoint string, listening string) error { + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithInsecure()} + err := health.RegisterHealthHandlerFromEndpoint(ctx, mux, endpoint, opts) + {{- range .Services}} + err = {{ .Name }}.Register{{ .Name | Title }}HandlerFromEndpoint(ctx, mux, endpoint, opts) + {{- end}} + + if err != nil { + return err + } + + return http.ListenAndServe(listening, mux) +} + +func main() { + endpoint := "0.0.0.0:{{ .Network.Grpc.Port }}" + listening := "0.0.0.0:{{ .Network.Http.Port }}" + log.Printf("Starting http grpc gateway server on %v...", listening) + + if err := run(endpoint, listening); err != nil { + log.Fatal(err) + } +} diff --git a/templates/proto/makefile.tmpl b/templates/proto/makefile.tmpl index 417d2b6ec..b8d6a5bc5 100644 --- a/templates/proto/makefile.tmpl +++ b/templates/proto/makefile.tmpl @@ -56,12 +56,12 @@ generate-web: {{- if .Network.Http.Enabled }} generate-http: - mkdir -p gen/http + mkdir -p gen/go protoc ${PROTO_SOURCES} --grpc-gateway_out=logtostderr=true,paths=source_relative:proto --swagger_out=logtostderr=true:proto ./proto/health/*.proto {{- range .Services}} protoc ${PROTO_SOURCES} --grpc-gateway_out=logtostderr=true,paths=source_relative:proto --swagger_out=logtostderr=true:proto ./proto/{{ .Name }}/*.proto {{- end }} - cp -f -rv proto/proto/* gen/http + cp -f -rv proto/proto/* gen/go rm -rf proto/proto {{- end}} diff --git a/templator/templator.go b/templator/templator.go index 830b4ca81..e0e63aae0 100644 --- a/templator/templator.go +++ b/templator/templator.go @@ -6,12 +6,19 @@ import ( "text/template" ) +type DockerTemplator struct { + ApplicationDocker *template.Template + HttpGatewayDocker *template.Template + DockerIgnore *template.Template +} + type GoTemplator struct { GoMain *template.Template GoMod *template.Template GoModIDL *template.Template GoServer *template.Template GoHealthServer *template.Template + GoHttpGW *template.Template } type Templator struct { @@ -21,6 +28,7 @@ type Templator struct { ProtoHealthTemplate *template.Template ProtoServiceTemplate *template.Template Go *GoTemplator + Docker *DockerTemplator } func NewTemplator(box *packr.Box) *Templator { @@ -40,6 +48,7 @@ func NewTemplator(box *packr.Box) *Templator { Go: NewGoTemplator(box), Sprout: NewSproutTemplator(box), GitIgnore: NewGitIgnoreTemplator(box), + Docker: NewDockerFileTemplator(box), } } @@ -59,12 +68,16 @@ func NewGoTemplator(box *packr.Box) *GoTemplator { goMainTemplateSource, _ := box.FindString("golang/main.tmpl") goMainTemplate, _ := template.New("GoMainTemplate").Funcs(util.FuncMap).Parse(goMainTemplateSource) + goHttpTemplateSource, _ := box.FindString("golang/http_gw.tmpl") + goHttpTemplate, _ := template.New("GoHttpGWTemplate").Funcs(util.FuncMap).Parse(goHttpTemplateSource) + return &GoTemplator{ GoMain: goMainTemplate, GoMod: goModTemplate, GoModIDL: goModIDLTemplate, GoServer: goServerTemplate, GoHealthServer: goHealthServerTemplate, + GoHttpGW: goHttpTemplate, } } @@ -81,3 +94,20 @@ func NewGitIgnoreTemplator(box *packr.Box) *template.Template { template, _ := template.New("GitIgnore").Parse(templateSource) return template } + +func NewDockerFileTemplator(box *packr.Box) *DockerTemplator { + appTemplateSource, _ := box.FindString("docker/dockerfile_app.tmpl") + appTemplate, _ := template.New("AppDockerfile").Parse(appTemplateSource) + + httpTemplateSource, _ := box.FindString("docker/dockerfile_http.tmpl") + httpTemplate, _ := template.New("HttpDockerfile").Parse(httpTemplateSource) + + ignoreTemplateSource, _ := box.FindString("docker/dockerignore.tmpl") + ignoreTemplate, _ := template.New("Dockerignore").Parse(ignoreTemplateSource) + + return &DockerTemplator{ + ApplicationDocker: appTemplate, + HttpGatewayDocker: httpTemplate, + DockerIgnore: ignoreTemplate, + } +} diff --git a/util/util.go b/util/util.go index 68646c046..36444ffaf 100644 --- a/util/util.go +++ b/util/util.go @@ -3,6 +3,8 @@ package util import ( "os" "strings" + "fmt" + "log" "text/template" ) @@ -17,3 +19,21 @@ func CreateDirIfDoesNotExist(path string) error { var FuncMap = template.FuncMap{ "Title": strings.Title, } + +func TemplateFileIfDoesNotExist(fileDir string, fileName string, template *template.Template, data interface{}) { + fullFilePath := fmt.Sprintf("%v/%v", fileDir, fileName) + + if _, err := os.Stat(fullFilePath); os.IsNotExist(err) { + err := CreateDirIfDoesNotExist(fileDir) + f, err := os.Create(fullFilePath) + if err != nil { + log.Printf("Error creating file: %v", err) + } + err = template.Execute(f, data) + if err != nil { + log.Printf("Error templating: %v", err) + } + } else { + log.Printf("%v already exists. skipping.", fullFilePath) + } +}