From b758e6ec7c0328830158acbac556b57e23d0bad8 Mon Sep 17 00:00:00 2001 From: jamesread Date: Fri, 7 May 2021 18:01:09 +0100 Subject: [PATCH] Initial commit --- Makefile | 8 ++++++ OliveTin.proto | 36 +++++++++++++++++++++++++ cmd/OliveTin/main.go | 54 +++++++++++++++++++++++++++++++++++++ go.mod | 21 +++++++++++++++ pkg/config/config.go | 45 +++++++++++++++++++++++++++++++ pkg/grpcApi/grpcApi.go | 61 ++++++++++++++++++++++++++++++++++++++++++ pkg/restApi/restapi.go | 57 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 282 insertions(+) create mode 100644 Makefile create mode 100644 OliveTin.proto create mode 100644 cmd/OliveTin/main.go create mode 100644 go.mod create mode 100644 pkg/config/config.go create mode 100644 pkg/grpcApi/grpcApi.go create mode 100644 pkg/restApi/restapi.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1b60d7b --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +default: + go build -o OliveTin github.com/jamesread/OliveTin/cmd/OliveTin + +grpc: + protoc -I.:/usr/share/gocode/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/ --go_out=plugins=grpc:gen/grpc/ OliveTin.proto + protoc -I.:/usr/share/gocode/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/ --grpc-gateway_out=gen/grpc --grpc-gateway_opt paths=source_relative --grpc-gateway_opt generate_unbound_methods=true OliveTin.proto + +.PHONY: grpc diff --git a/OliveTin.proto b/OliveTin.proto new file mode 100644 index 0000000..f112d33 --- /dev/null +++ b/OliveTin.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; + +message ActionButton { + string title = 1; + string icon = 2; +} + +message GetButtonsResponse { + string title = 1; + repeated ActionButton actions = 2; +} + +message GetButtonsRequest {} + +message StartActionRequest { + string actionName = 1; +} + +message StartActionResponse {} + +service OliveTinApi { + rpc GetButtons(GetButtonsRequest) returns (GetButtonsResponse) { + option (google.api.http) = { + get: "/GetButtons" + }; + } + + rpc StartAction(StartActionRequest) returns (StartActionResponse) { + option (google.api.http) = { + get: "/StartAction" + }; + } + +} diff --git a/cmd/OliveTin/main.go b/cmd/OliveTin/main.go new file mode 100644 index 0000000..3735fb9 --- /dev/null +++ b/cmd/OliveTin/main.go @@ -0,0 +1,54 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + + restApi "github.com/jamesread/OliveTin/pkg/restApi" + grpcApi "github.com/jamesread/OliveTin/pkg/grpcApi" + + config "github.com/jamesread/OliveTin/pkg/config" + "github.com/spf13/viper" + "os" +) + +var ( + cfg *config.Config; +) + +func init() { + log.Info("OliveTin initializing"); + log.SetLevel(log.DebugLevel) // Default to debug, to catch cfg issues + + viper.AutomaticEnv(); + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + viper.AddConfigPath("/etc/olivetin/") + + if err := viper.ReadInConfig(); err != nil { + log.Panicf("Config file error %s", err); + }; + + cfg = config.DefaultConfig(); + + if err := viper.UnmarshalExact(cfg); err != nil { + log.Errorf("Config unmarshal error %+v", err) + os.Exit(1); + } + + log.SetLevel(cfg.GetLogLevel()) + + viper.WatchConfig(); +} + +func main() { + log.WithFields(log.Fields { + "listenPortRestActions": cfg.ListenPortRestActions, + "listenPortWebUi": cfg.ListenPortWebUi, + }).Info("OliveTin started"); + + log.Debugf("%+v", cfg) + + go grpcApi.Start() + + restApi.Start(); +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e2e4e7f --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module github.com/jamesread/OliveTin + +go 1.15 + +require ( + github.com/golang/protobuf v1.5.2 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 + github.com/sirupsen/logrus v1.8.1 + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.7.0 // indirect + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/text v0.3.5 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20210224155714-063164c882e6 + google.golang.org/grpc v1.37.0 + gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..76d136f --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,45 @@ +package config + +import ( + log "github.com/sirupsen/logrus" + "strings" +) + +type ActionButton struct { + Title string + Icon string + Css map[string]string `mapstructure:omitempty` +} + +type Entity struct { + Title string + Icon string + ActionButtons []ActionButton `mapstructure:"actions"` + Css map[string]string +} + +type Config struct { + ListenPortRestActions int + ListenPortWebUi int + LogLevel string + ActionButtons []ActionButton `mapstructure:"actions"` + Entities []Entity `mapstructure:omitempty` +} + +func DefaultConfig() *Config { + config := Config{}; + config.ListenPortRestActions = 1337; + config.ListenPortWebUi = 1339; + config.LogLevel = "INFO" + + return &config +} + +func (cfg *Config) GetLogLevel() (log.Level) { + switch (strings.ToUpper(cfg.LogLevel)) { + case "INFO": return log.InfoLevel; + case "WARN": return log.WarnLevel; + case "DEBUG": return log.DebugLevel; + default: return log.DebugLevel; + } +} diff --git a/pkg/grpcApi/grpcApi.go b/pkg/grpcApi/grpcApi.go new file mode 100644 index 0000000..52aa452 --- /dev/null +++ b/pkg/grpcApi/grpcApi.go @@ -0,0 +1,61 @@ +package grpcApi; + +import ( + "google.golang.org/grpc" + pb "github.com/jamesread/OliveTin/gen/grpc" + "fmt" + "net" + log "github.com/sirupsen/logrus" + ctx "context" +) + +type OliveTinApi struct { + +} + +func (api *OliveTinApi) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) { + res := &pb.StartActionResponse{} + + log.WithFields(log.Fields{ + "actionName": req.ActionName, + }).Infof("StartAction") + + return res, nil +} + +func (api *OliveTinApi) GetButtons(ctx ctx.Context, req *pb.GetButtonsRequest) (*pb.GetButtonsResponse, error) { + res := &pb.GetButtonsResponse{}; + + btn1 := pb.ActionButton { + Title: "foo", + Icon: "🇦", + }; + + btn2 := pb.ActionButton { + Title: "bar", + Icon: "🇦", + }; + + res.Actions = append(res.Actions, &btn1); + res.Actions = append(res.Actions, &btn2); + + return res, nil +} + +func Start() { + port := 1337; + lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)); + + if err != nil { + log.Fatalf("Failed to listen - %v", err); + } + + grpcServer := grpc.NewServer(); + pb.RegisterOliveTinApiServer(grpcServer, newServer()); + grpcServer.Serve(lis) +} + +func newServer() (*OliveTinApi) { + server := OliveTinApi {}; + return &server; +} diff --git a/pkg/restApi/restapi.go b/pkg/restApi/restapi.go new file mode 100644 index 0000000..56de026 --- /dev/null +++ b/pkg/restApi/restapi.go @@ -0,0 +1,57 @@ +package restApi; + +import ( + "google.golang.org/grpc" + "fmt" + log "github.com/sirupsen/logrus" + "context" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "net/http" + "strings" + + gw "github.com/jamesread/OliveTin/gen/grpc" +) + + +func Start() (error) { + port := 1339; + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + //lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)); + + mux := runtime.NewServeMux() + opts := []grpc.DialOption{grpc.WithInsecure()} + + err := gw.RegisterOliveTinApiHandlerFromEndpoint(ctx, mux, "127.0.0.1:1337", opts) + + if err != nil { + log.Fatalf("gw error %v", err) + } + + return http.ListenAndServe(fmt.Sprintf(":%d", port), allowCors(mux)) +} + +func allowCors(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if origin := r.Header.Get("Origin"); origin != "" { + w.Header().Set("Access-Control-Allow-Origin", origin) + if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { + preflightHandler(w, r) + return + } + } + h.ServeHTTP(w, r) + }) +} + +func preflightHandler(w http.ResponseWriter, r *http.Request) { + headers := []string{"Content-Type", "Accept"} + w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ",")) + methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"} + w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ",")) + log.Infof("preflight request for %s", r.URL.Path) + return +}