From dc222a105268e712c85a860ff6c9ce2f72dbe61b Mon Sep 17 00:00:00 2001 From: Stefano Da Ros Date: Mon, 19 Nov 2018 17:13:57 +0100 Subject: [PATCH] Add support for running as windows service --- Makefile | 11 ++-- build/build.sh | 2 +- config/config.yml | 7 ++- go.mod | 1 + go.sum | 2 + internal/app/endpoint/endpoint.go | 4 +- internal/pkg/config/config.go | 29 +++++++-- internal/pkg/log/logging.go | 16 +++-- main.go | 99 +++++++++++++++++++++++++++---- 9 files changed, 138 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 781e060..353832c 100644 --- a/Makefile +++ b/Makefile @@ -30,10 +30,10 @@ OS ?= linux STAGE ?= production # This version-strategy uses git tags to set the version string -#VERSION := $(shell git describe --tags --always --dirty) +VERSION := $(shell git describe --tags --always --dirty) # # This version-strategy uses a manual value to set the version string -VERSION := 0.1.0 +#VERSION := 0.1.0 ### ### These variables should not need tweaking. @@ -60,7 +60,7 @@ endif IMAGE := $(REGISTRY)/$(BIN)-$(ARCH)-$(OS) -BUILD_IMAGE ?= golang:1.9.2-alpine3.6 +BUILD_IMAGE ?= golang:1.11-stretch # If you want to build all binaries, see the 'all-build' rule. # If you want to build all containers, see the 'all-container' rule. # If you want to build AND push all containers, see the 'all-push' rule. @@ -74,10 +74,11 @@ bin/$(ARCH)/$(OS)/$(BIN): build-dirs container-testing -ti \ --rm \ -u $$(id -u):$$(id -g) \ + -e GO111MODULE=on \ -v "$$(pwd)/.go:/go" \ -v "$$(pwd):/go/src/$(PKG)" \ - -v "$$(pwd)/bin/$(ARCH)/$(OS):/go/bin" \ - -v "$$(pwd)/bin/$(ARCH)/$(OS):/go/bin/$(OS)_$(ARCH)" \ + -v "$$(pwd)/bin/$(ARCH)/$(OS):/go/bin" \ + -v "$$(pwd)/bin/$(ARCH)/$(OS):/go/bin/$(OS)_$(ARCH)" \ -v "$$(pwd)/.go/std/$(ARCH):/usr/local/go/pkg/$(OS)_$(ARCH)_static" \ -w /go/src/$(PKG) \ $(IMAGE):$(VERSION)-testing \ diff --git a/build/build.sh b/build/build.sh index 97b4e17..e478053 100755 --- a/build/build.sh +++ b/build/build.sh @@ -47,5 +47,5 @@ export GOARCH="${ARCH}" export GOOS="${OS}" go install \ - -ldflags "-X ${PKG}/pkg/version.VERSION=${VERSION} main.version=${VERSION}"\ + -ldflags "-X ${PKG}/pkg/version.VERSION=${VERSION} -X main.version=${VERSION}"\ ./... diff --git a/config/config.yml b/config/config.yml index 51d25bd..2daa4c2 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,6 +1,11 @@ +name: workflow-connector +displayName: Signavio Workflow Connector +description: > + A web service which provides a Workflow Accelerator conform + RESTful API on top of standard SQL Databases port: 443 database: - driver: sqlite + driver: sqlite3 url: test.db tls: enabled: false diff --git a/go.mod b/go.mod index cfac373..1aa59aa 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f github.com/go-sql-driver/mysql v1.4.0 github.com/gorilla/mux v1.6.2 + github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b github.com/lib/pq v1.0.0 github.com/mattn/go-sqlite3 v1.10.0 github.com/satori/go.uuid v1.2.0 diff --git a/go.sum b/go.sum index f228a44..f4a3411 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGE github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b h1:vfiqKno48aUndBMjTeWFpCExNnTf2Xnd6d228L4EfTQ= +github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b/go.mod h1:10UU/bEkzh2iEN6aYzbevY7J6p03KO5siTxQWXMEerg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/lib/pq v0.0.0-20180523175426-90697d60dd84 h1:it29sI2IM490luSc3RAhp5WuCYnc6RtbfLVAB7nmC5M= github.com/lib/pq v0.0.0-20180523175426-90697d60dd84/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= diff --git a/internal/app/endpoint/endpoint.go b/internal/app/endpoint/endpoint.go index 6cac2fd..e5001b6 100644 --- a/internal/app/endpoint/endpoint.go +++ b/internal/app/endpoint/endpoint.go @@ -50,8 +50,8 @@ func NewEndpoint(cfg config.Config) (Endpoint, error) { switch cfg.Database.Driver { case "sqlserver": return sql.NewBackend("sqlserver"), nil - case "sqlite": - return sql.NewBackend("sqlite"), nil + case "sqlite3": + return sql.NewBackend("sqlite3"), nil case "mysql": return sql.NewBackend("mysql"), nil case "postgres": diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 0f47f51..21acda3 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -11,14 +11,16 @@ import ( ) // Options is populated by this package's init() function -// TODO It should be a singleton var Options Config // Config defines the data structures which can be used and configured // in the config.yaml file and other relevant data structures type Config struct { - Port string - Database struct { + Name string + DisplayName string + Description string + Port string + Database struct { Driver string URL string Tables []*Table @@ -61,6 +63,13 @@ type configDir struct { val string } +// service is a command line flag that specifies which control +// should be sent to a service +type service struct { + name string + val string +} + // Initialize configuration file from typical directory locations and parse it func init() { db := &db{name: "db", val: ""} @@ -69,10 +78,14 @@ func init() { configDir := &configDir{name: "config-dir", val: "config"} flag.StringVar(&configDir.val, "config-dir", "", "specify location to config directory") viper.BindFlagValue("configDir", configDir) + serviceControl := &service{name: "service", val: ""} + flag.StringVar(&serviceControl.val, "service", "", "specify control to send to service") + viper.BindFlagValue("service", serviceControl) flag.Parse() viper.SetConfigName("config") if configDir.ValueString() == "" { viper.AddConfigPath("config") + viper.AddConfigPath(filepath.Join("C:\\Program Files\\Workflow Connector\\", "config")) viper.AddConfigPath(filepath.Join("../../../", "config")) viper.AddConfigPath(filepath.Join("../../../../", "config")) } else { @@ -84,14 +97,14 @@ func init() { replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { - log.Fatalf("Can not parse config file: %v\n", err) + log.When(true).Fatalf("Can not parse config file: %v\n", err) } if err := viper.Unmarshal(&Options); err != nil { - log.Fatalf("Unable to decode config file into struct: %s", err) + log.When(true).Fatalf("Unable to decode config file into struct: %s", err) } descriptorFile, err := os.Open(descriptorFilePath()) if err != nil { - log.Fatalf("Unable to open descriptor.json file: %v\n", err) + log.When(true).Fatalf("Unable to open descriptor.json file: %v\n", err) } Options.Descriptor = ParseDescriptorFile(descriptorFile) for _, td := range Options.Descriptor.TypeDescriptors { @@ -113,3 +126,7 @@ func (f configDir) HasChanged() bool { return false } func (f configDir) Name() string { return f.name } func (f configDir) ValueString() string { return f.val } func (f configDir) ValueType() string { return "string" } +func (f service) HasChanged() bool { return false } +func (f service) Name() string { return f.name } +func (f service) ValueString() string { return f.val } +func (f service) ValueType() string { return "string" } diff --git a/internal/pkg/log/logging.go b/internal/pkg/log/logging.go index 7323db8..b4606bf 100644 --- a/internal/pkg/log/logging.go +++ b/internal/pkg/log/logging.go @@ -23,11 +23,15 @@ func (l Logger) Infoln(v ...interface{}) { fmt.Println(v...) } } -func Fatalln(v ...interface{}) { - fmt.Println(v...) - os.Exit(1) +func (l Logger) Fatalln(v ...interface{}) { + if l { + fmt.Println(v...) + os.Exit(1) + } } -func Fatalf(format string, v ...interface{}) { - fmt.Printf(format, v...) - os.Exit(1) +func (l Logger) Fatalf(format string, v ...interface{}) { + if l { + fmt.Printf(format, v...) + os.Exit(1) + } } diff --git a/main.go b/main.go index cb0a775..39bfbbd 100644 --- a/main.go +++ b/main.go @@ -1,34 +1,109 @@ package main import ( + "context" + "net/http" + "os" + + "github.com/kardianos/service" "github.com/signavio/workflow-connector/internal/app/endpoint" "github.com/signavio/workflow-connector/internal/app/server" "github.com/signavio/workflow-connector/internal/pkg/config" - "github.com/signavio/workflow-connector/internal/pkg/log" + "github.com/spf13/viper" ) -var version string +var ( + version string + logger service.Logger +) -func main() { - log.When(true).Infof("starting workflow connector v%s\n", version) +type app struct { + server *http.Server +} + +func (a *app) Start(s service.Service) error { + logger.Infof("starting workflow connector %s\n", version) + go a.run() + return nil +} +func (a *app) Stop(s service.Service) error { + logger.Infof("\nstopping workflow connector %s\n", version) + if err := a.server.Shutdown(context.Background()); err != nil { + logger.Infof("unable to shutdown server cleanly: %s\n", err) + } + if service.Interactive() { + os.Exit(0) + } + return nil +} +func (a *app) run() { endpoint, err := endpoint.NewEndpoint(config.Options) if err != nil { - log.Fatalln(err) + logger.Errorf("unable to create new endpoint: %s\n", err) + os.Exit(1) } - log.When(true).Infoln("[endpoint] initialize backend") err = endpoint.Open( config.Options.Database.Driver, config.Options.Database.URL, ) if err != nil { - log.Fatalln(err) + logger.Errorf("unable to initialize backend: %s\n", err) + os.Exit(1) } - server := server.NewServer(config.Options, endpoint) - println("[server] ready and listening on :" + config.Options.Port) + a.server = server.NewServer(config.Options, endpoint) + logger.Infof( + "server is ready and listening on port %s\n", + config.Options.Port, + ) if config.Options.TLS.Enabled { - log.Fatalln(server.ListenAndServeTLS(config.Options.TLS.PublicKey, - config.Options.TLS.PrivateKey)) + err := a.server.ListenAndServeTLS( + config.Options.TLS.PublicKey, + config.Options.TLS.PrivateKey, + ) + if err != http.ErrServerClosed { + logger.Errorf("unable to start http server: %s\n", err) + os.Exit(1) + } } else { - log.Fatalln(server.ListenAndServe()) + err := a.server.ListenAndServe() + if err != http.ErrServerClosed { + logger.Errorf("unable to start http server: %s\n", err) + os.Exit(1) + } + } +} + +func main() { + a := &app{} + svc, err := service.New( + a, + &service.Config{ + Name: config.Options.Name, + DisplayName: config.Options.DisplayName, + Description: config.Options.Description, + }, + ) + if err != nil { + logger.Errorf("unable to create service: %s\n", err) + os.Exit(1) + } + logger, err = svc.Logger(nil) + if err != nil { + logger.Errorf("unable to initialize logger: %s\n", err) + os.Exit(1) + } + serviceControl, ok := viper.Get("service").(string) + if ok && serviceControl != "" { + // Execute user specified control on service + if err := service.Control(svc, serviceControl); err != nil { + logger.Error(err) + os.Exit(1) + } + return + } + err = svc.Run() + if err != nil { + logger.Errorf("unable to run the service: %s\n", err) + os.Exit(1) } }