initial commit

This commit is contained in:
strNophix 2022-02-04 20:23:04 +01:00
parent 0c44f26522
commit 2b614f6bd3
21 changed files with 1236 additions and 1 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2022 strNophix
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

6
Makefile Normal file
View File

@ -0,0 +1,6 @@
.PHONY: protos
protos:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
proto/gocron-server.proto

26
cmd/config.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
gocron_server "github.com/strnophix/gocron-server/pkg"
)
type ServerConfig struct {
Host string `toml:"host"`
}
type UnitConfig struct {
Name string `toml:"name"`
Command string `toml:"command"`
Cron string `toml:"cron"`
}
func (u *UnitConfig) ToSchedulerUnit() *gocron_server.SchedulerUnit {
cmd := gocron_server.NewUnitExecCmd(u.Command)
unit := gocron_server.NewSchedulerUnit(u.Name, u.Cron, cmd)
return unit
}
type Config struct {
Server ServerConfig `toml:"server"`
Unit []UnitConfig `toml:"unit"`
}

56
cmd/main.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"log"
"net"
"os"
"github.com/BurntSushi/toml"
gocron_server "github.com/strnophix/gocron-server/pkg"
pb "github.com/strnophix/gocron-server/pkg/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
if len(os.Args) < 2 {
log.Fatalln("Expected a gocron-server config as the first argument.")
}
cfgPath := os.Args[1]
cfg := Config{}
_, err := toml.DecodeFile(cfgPath, &cfg)
if err != nil {
log.Fatalln(err)
}
l, err := net.Listen("tcp", cfg.Server.Host)
if err != nil {
log.Fatalf("Failed to start server on %s: %v", cfg.Server.Host, err)
}
log.Printf("gocron-server is running on %s\n", cfg.Server.Host)
gs := grpc.NewServer()
s := gocron_server.NewSchedulerService()
defer s.Shutdown()
for _, unitCfg := range cfg.Unit {
unit := unitCfg.ToSchedulerUnit()
err = s.AddUnit(unit)
if err != nil {
log.Fatalln(err)
}
log.Printf("Succesfully added unit %s\n", unit.Name)
}
pb.RegisterSchedulerServer(gs, s)
reflection.Register(gs)
if err := gs.Serve(l); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}

12
examples/config.toml Normal file
View File

@ -0,0 +1,12 @@
# An example config that can be supplied to the CLI
[server]
host=":9092"
[[unit]]
name="echo"
command="echo heyooo"
cron="* * * * *"
[[unit]]
name="notify"
command="notify-send heyooo"

57
examples/counter.go Normal file
View File

@ -0,0 +1,57 @@
// Sample usage of the gocron-server pkg
package main
import (
"fmt"
"log"
"net"
gocron_server "github.com/strnophix/gocron-server/pkg"
pb "github.com/strnophix/gocron-server/pkg/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
const (
port = ":9092"
)
type Counter struct {
Current int
}
func (c *Counter) Increment() (string, error) {
c.Current += 1
fmt.Printf("Currently: %d\n", c.Current)
return fmt.Sprint(c.Current), nil
}
func main() {
l, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("Failed to start server on %s: %v", port, err)
}
log.Printf("Running on port %s\n", port)
c := &Counter{Current: 1}
gs := grpc.NewServer()
s := gocron_server.NewSchedulerService()
defer s.Shutdown()
incr := gocron_server.NewUnitExecFn(c.Increment)
unit := gocron_server.NewManualUnit("incr", incr)
err = s.AddUnit(unit)
if err != nil {
log.Fatalln(err)
}
pb.RegisterSchedulerServer(gs, s)
reflection.Register(gs)
if err := gs.Serve(l); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}

21
go.mod Normal file
View File

@ -0,0 +1,21 @@
module github.com/strnophix/gocron-server
go 1.17
require (
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/go-co-op/gocron v1.11.0 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.25.0 // indirect
)

129
go.sum Normal file
View File

@ -0,0 +1,129 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-co-op/gocron v1.11.0 h1:ujOMubCpGcTxnnR/9vJIPIEpgwuAjbueAYqJRNr+nHg=
github.com/go-co-op/gocron v1.11.0/go.mod h1:qtlsoMpHlSdIZ3E/xuZzrrAbeX3u5JtPvWf2TcdutU0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

56
pkg/broadcaster.go Normal file
View File

@ -0,0 +1,56 @@
package gocron_server
import (
pb "github.com/strnophix/gocron-server/pkg/proto"
)
type Streamer interface {
Send(*pb.ListenJobResponse) error
}
type Streams []Streamer
type EventBroadcaster struct {
streams Streams
}
func NewEventBroadcaster() *EventBroadcaster {
return &EventBroadcaster{
streams: make(Streams, 0),
}
}
func (b *EventBroadcaster) Subscribe(stream Streamer) {
b.streams = append(b.streams, stream)
}
func (b *EventBroadcaster) Publish(resp *pb.ListenJobResponse) {
closed := make([]int, 0)
for idx, stream := range b.streams {
if err := stream.Send(resp); err != nil {
closed = append(closed, idx)
}
}
if len(closed) == len(b.streams) {
b.streams = nil
return
}
for idx := len(closed) - 1; idx > -1; idx-- {
removeStream(b.streams, idx)
}
}
func (b *EventBroadcaster) SubscriberCount() int {
return len(b.streams)
}
func removeStream(s Streams, i int) Streams {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}
func NewBroadcastResponse(name, result string) *pb.ListenJobResponse {
return &pb.ListenJobResponse{JobName: name, JobResult: result}
}

43
pkg/broadcaster_test.go Normal file
View File

@ -0,0 +1,43 @@
package gocron_server_test
import (
"fmt"
"testing"
gocron_server "github.com/strnophix/gocron-server/pkg"
pb "github.com/strnophix/gocron-server/pkg/proto"
)
type TestStreamer struct {
CalledOnce bool
}
func NewTestStreamer() *TestStreamer {
return &TestStreamer{CalledOnce: false}
}
func (ts *TestStreamer) Send(stream *pb.ListenJobResponse) error {
if ts.CalledOnce == true {
return fmt.Errorf("Send can only be called once")
}
ts.CalledOnce = true
return nil
}
func TestPublish(t *testing.T) {
cl := NewTestStreamer()
eb := gocron_server.NewEventBroadcaster()
eb.Subscribe(cl)
msg := gocron_server.NewBroadcastResponse("test", "Content :)")
eb.Publish(msg)
if cl.CalledOnce == false {
t.Fatalf("The Send function should have been called on the stream")
}
eb.Publish(msg)
if eb.SubscriberCount() != 0 {
t.Fatalf("The second call of Send should have unsubscribed the stream")
}
}

View File

@ -0,0 +1,347 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.19.2
// source: proto/gocron-server.proto
package gocron_server
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RunJobRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UnitName string `protobuf:"bytes,1,opt,name=UnitName,proto3" json:"UnitName,omitempty"`
RunAt int64 `protobuf:"varint,10,opt,name=RunAt,proto3" json:"RunAt,omitempty"`
}
func (x *RunJobRequest) Reset() {
*x = RunJobRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_gocron_server_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RunJobRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RunJobRequest) ProtoMessage() {}
func (x *RunJobRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_gocron_server_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RunJobRequest.ProtoReflect.Descriptor instead.
func (*RunJobRequest) Descriptor() ([]byte, []int) {
return file_proto_gocron_server_proto_rawDescGZIP(), []int{0}
}
func (x *RunJobRequest) GetUnitName() string {
if x != nil {
return x.UnitName
}
return ""
}
func (x *RunJobRequest) GetRunAt() int64 {
if x != nil {
return x.RunAt
}
return 0
}
type RunJobResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *RunJobResponse) Reset() {
*x = RunJobResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_gocron_server_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RunJobResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RunJobResponse) ProtoMessage() {}
func (x *RunJobResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_gocron_server_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RunJobResponse.ProtoReflect.Descriptor instead.
func (*RunJobResponse) Descriptor() ([]byte, []int) {
return file_proto_gocron_server_proto_rawDescGZIP(), []int{1}
}
type ListenJobRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ListenJobRequest) Reset() {
*x = ListenJobRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_gocron_server_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListenJobRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListenJobRequest) ProtoMessage() {}
func (x *ListenJobRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_gocron_server_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListenJobRequest.ProtoReflect.Descriptor instead.
func (*ListenJobRequest) Descriptor() ([]byte, []int) {
return file_proto_gocron_server_proto_rawDescGZIP(), []int{2}
}
type ListenJobResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
JobName string `protobuf:"bytes,1,opt,name=JobName,proto3" json:"JobName,omitempty"`
JobResult string `protobuf:"bytes,2,opt,name=JobResult,proto3" json:"JobResult,omitempty"`
}
func (x *ListenJobResponse) Reset() {
*x = ListenJobResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_gocron_server_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListenJobResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListenJobResponse) ProtoMessage() {}
func (x *ListenJobResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_gocron_server_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListenJobResponse.ProtoReflect.Descriptor instead.
func (*ListenJobResponse) Descriptor() ([]byte, []int) {
return file_proto_gocron_server_proto_rawDescGZIP(), []int{3}
}
func (x *ListenJobResponse) GetJobName() string {
if x != nil {
return x.JobName
}
return ""
}
func (x *ListenJobResponse) GetJobResult() string {
if x != nil {
return x.JobResult
}
return ""
}
var File_proto_gocron_server_proto protoreflect.FileDescriptor
var file_proto_gocron_server_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x2d, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x67, 0x6f, 0x63,
0x72, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x41, 0x0a, 0x0d, 0x52, 0x75,
0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x55,
0x6e, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x55,
0x6e, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x75, 0x6e, 0x41, 0x74,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x52, 0x75, 0x6e, 0x41, 0x74, 0x22, 0x10, 0x0a,
0x0e, 0x52, 0x75, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4a, 0x6f, 0x62,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x4e,
0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4a, 0x6f, 0x62, 0x4e, 0x61,
0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x32, 0xa5, 0x01, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x12, 0x45,
0x0a, 0x06, 0x52, 0x75, 0x6e, 0x4a, 0x6f, 0x62, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x63, 0x72, 0x6f,
0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x4a, 0x6f, 0x62, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x5f,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4a,
0x6f, 0x62, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4a, 0x6f, 0x62, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x72, 0x6e, 0x6f, 0x70, 0x68, 0x69, 0x78,
0x2f, 0x67, 0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3b, 0x67,
0x6f, 0x63, 0x72, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_gocron_server_proto_rawDescOnce sync.Once
file_proto_gocron_server_proto_rawDescData = file_proto_gocron_server_proto_rawDesc
)
func file_proto_gocron_server_proto_rawDescGZIP() []byte {
file_proto_gocron_server_proto_rawDescOnce.Do(func() {
file_proto_gocron_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_gocron_server_proto_rawDescData)
})
return file_proto_gocron_server_proto_rawDescData
}
var file_proto_gocron_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_proto_gocron_server_proto_goTypes = []interface{}{
(*RunJobRequest)(nil), // 0: gocron_server.RunJobRequest
(*RunJobResponse)(nil), // 1: gocron_server.RunJobResponse
(*ListenJobRequest)(nil), // 2: gocron_server.ListenJobRequest
(*ListenJobResponse)(nil), // 3: gocron_server.ListenJobResponse
}
var file_proto_gocron_server_proto_depIdxs = []int32{
0, // 0: gocron_server.Scheduler.RunJob:input_type -> gocron_server.RunJobRequest
2, // 1: gocron_server.Scheduler.ListenJobs:input_type -> gocron_server.ListenJobRequest
1, // 2: gocron_server.Scheduler.RunJob:output_type -> gocron_server.RunJobResponse
3, // 3: gocron_server.Scheduler.ListenJobs:output_type -> gocron_server.ListenJobResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proto_gocron_server_proto_init() }
func file_proto_gocron_server_proto_init() {
if File_proto_gocron_server_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_gocron_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RunJobRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_gocron_server_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RunJobResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_gocron_server_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListenJobRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_gocron_server_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListenJobResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_gocron_server_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proto_gocron_server_proto_goTypes,
DependencyIndexes: file_proto_gocron_server_proto_depIdxs,
MessageInfos: file_proto_gocron_server_proto_msgTypes,
}.Build()
File_proto_gocron_server_proto = out.File
file_proto_gocron_server_proto_rawDesc = nil
file_proto_gocron_server_proto_goTypes = nil
file_proto_gocron_server_proto_depIdxs = nil
}

View File

@ -0,0 +1,23 @@
syntax = "proto3";
package gocron_server;
option go_package = "github.com/strnophix/gocron-server;gocron_server";
service Scheduler {
rpc RunJob(RunJobRequest) returns (RunJobResponse);
rpc ListenJobs(ListenJobRequest) returns (stream ListenJobResponse);
}
message RunJobRequest {
string UnitName = 1;
int64 RunAt = 10;
}
message RunJobResponse {}
message ListenJobRequest {}
message ListenJobResponse {
string JobName = 1;
string JobResult = 2;
}

View File

@ -0,0 +1,165 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package gocron_server
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// SchedulerClient is the client API for Scheduler service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type SchedulerClient interface {
RunJob(ctx context.Context, in *RunJobRequest, opts ...grpc.CallOption) (*RunJobResponse, error)
ListenJobs(ctx context.Context, in *ListenJobRequest, opts ...grpc.CallOption) (Scheduler_ListenJobsClient, error)
}
type schedulerClient struct {
cc grpc.ClientConnInterface
}
func NewSchedulerClient(cc grpc.ClientConnInterface) SchedulerClient {
return &schedulerClient{cc}
}
func (c *schedulerClient) RunJob(ctx context.Context, in *RunJobRequest, opts ...grpc.CallOption) (*RunJobResponse, error) {
out := new(RunJobResponse)
err := c.cc.Invoke(ctx, "/gocron_server.Scheduler/RunJob", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *schedulerClient) ListenJobs(ctx context.Context, in *ListenJobRequest, opts ...grpc.CallOption) (Scheduler_ListenJobsClient, error) {
stream, err := c.cc.NewStream(ctx, &Scheduler_ServiceDesc.Streams[0], "/gocron_server.Scheduler/ListenJobs", opts...)
if err != nil {
return nil, err
}
x := &schedulerListenJobsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Scheduler_ListenJobsClient interface {
Recv() (*ListenJobResponse, error)
grpc.ClientStream
}
type schedulerListenJobsClient struct {
grpc.ClientStream
}
func (x *schedulerListenJobsClient) Recv() (*ListenJobResponse, error) {
m := new(ListenJobResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// SchedulerServer is the server API for Scheduler service.
// All implementations must embed UnimplementedSchedulerServer
// for forward compatibility
type SchedulerServer interface {
RunJob(context.Context, *RunJobRequest) (*RunJobResponse, error)
ListenJobs(*ListenJobRequest, Scheduler_ListenJobsServer) error
mustEmbedUnimplementedSchedulerServer()
}
// UnimplementedSchedulerServer must be embedded to have forward compatible implementations.
type UnimplementedSchedulerServer struct {
}
func (UnimplementedSchedulerServer) RunJob(context.Context, *RunJobRequest) (*RunJobResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RunJob not implemented")
}
func (UnimplementedSchedulerServer) ListenJobs(*ListenJobRequest, Scheduler_ListenJobsServer) error {
return status.Errorf(codes.Unimplemented, "method ListenJobs not implemented")
}
func (UnimplementedSchedulerServer) mustEmbedUnimplementedSchedulerServer() {}
// UnsafeSchedulerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SchedulerServer will
// result in compilation errors.
type UnsafeSchedulerServer interface {
mustEmbedUnimplementedSchedulerServer()
}
func RegisterSchedulerServer(s grpc.ServiceRegistrar, srv SchedulerServer) {
s.RegisterService(&Scheduler_ServiceDesc, srv)
}
func _Scheduler_RunJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RunJobRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SchedulerServer).RunJob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/gocron_server.Scheduler/RunJob",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SchedulerServer).RunJob(ctx, req.(*RunJobRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Scheduler_ListenJobs_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ListenJobRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(SchedulerServer).ListenJobs(m, &schedulerListenJobsServer{stream})
}
type Scheduler_ListenJobsServer interface {
Send(*ListenJobResponse) error
grpc.ServerStream
}
type schedulerListenJobsServer struct {
grpc.ServerStream
}
func (x *schedulerListenJobsServer) Send(m *ListenJobResponse) error {
return x.ServerStream.SendMsg(m)
}
// Scheduler_ServiceDesc is the grpc.ServiceDesc for Scheduler service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Scheduler_ServiceDesc = grpc.ServiceDesc{
ServiceName: "gocron_server.Scheduler",
HandlerType: (*SchedulerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "RunJob",
Handler: _Scheduler_RunJob_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "ListenJobs",
Handler: _Scheduler_ListenJobs_Handler,
ServerStreams: true,
},
},
Metadata: "proto/gocron-server.proto",
}

106
pkg/scheduler_service.go Normal file
View File

@ -0,0 +1,106 @@
package gocron_server
import (
"context"
"fmt"
"time"
"github.com/go-co-op/gocron"
pb "github.com/strnophix/gocron-server/pkg/proto"
)
type JobFunc func() (string, error)
type UnitStore map[string]*SchedulerUnit
type JobStore map[string]*gocron.Job
type SchedulerService struct {
pb.UnimplementedSchedulerServer
UnitStore
JobStore
EventBroadcaster
Scheduler *gocron.Scheduler
}
func NewSchedulerService() *SchedulerService {
scheduler := gocron.NewScheduler(time.UTC)
scheduler.StartAsync()
return &SchedulerService{
Scheduler: scheduler,
UnitStore: make(UnitStore),
JobStore: make(JobStore),
}
}
func (s *SchedulerService) Shutdown() {
s.Scheduler.Stop()
}
func (s *SchedulerService) AddUnit(unit *SchedulerUnit) error {
s.UnitStore[unit.Name] = unit
if unit.Cron != "" {
routine := s.BuildRoutine(unit)
job, err := s.Scheduler.Cron(unit.Cron).SingletonMode().Do(routine)
if err != nil {
return err
}
s.JobStore[unit.Name] = job
}
return nil
}
func (s *SchedulerService) BuildRoutine(unit *SchedulerUnit) func() {
return func() {
out, err := unit.Exec.Call()
if err != nil {
msg := NewBroadcastResponse(unit.Name, err.Error())
s.EventBroadcaster.Publish(msg)
return
}
msg := NewBroadcastResponse(unit.Name, out)
s.EventBroadcaster.Publish(msg)
}
}
func NewRunJobError(reason string) (*pb.RunJobResponse, error) {
return &pb.RunJobResponse{}, fmt.Errorf(reason)
}
func NewRunJobSucces() (*pb.RunJobResponse, error) {
return &pb.RunJobResponse{}, nil
}
func (s *SchedulerService) RunJob(ctx context.Context, req *pb.RunJobRequest) (*pb.RunJobResponse, error) {
unit, exists := s.UnitStore[req.UnitName]
if !exists {
return NewRunJobError(fmt.Sprintf("Unit with name %s does not exist", req.UnitName))
}
routine := s.BuildRoutine(unit)
if req.RunAt != 0 {
ts := time.Unix(req.RunAt, 0).UTC()
job, err := s.Scheduler.Every(1).Day().At(ts).LimitRunsTo(1).SingletonMode().Do(routine)
if err != nil {
fmt.Printf("Unix run error: %v", err)
}
s.JobStore[unit.Name] = job
return NewRunJobSucces()
}
go routine()
return NewRunJobSucces()
}
func (s *SchedulerService) ListenJobs(req *pb.ListenJobRequest, stream pb.Scheduler_ListenJobsServer) error {
s.EventBroadcaster.Subscribe(stream)
<-stream.Context().Done()
return nil
}

View File

@ -0,0 +1,71 @@
package gocron_server_test
import (
"context"
"log"
"net"
"testing"
"time"
gocron_server "github.com/strnophix/gocron-server/pkg"
pb "github.com/strnophix/gocron-server/pkg/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024 * 1024
var lis *bufconn.Listener
var c *Counter
func init() {
c = &Counter{Current: 1}
lis = bufconn.Listen(bufSize)
gs := grpc.NewServer()
s := gocron_server.NewSchedulerService()
defer s.Shutdown()
incr := gocron_server.NewUnitExecFn(c.Increment)
unit := gocron_server.NewManualUnit("incr", incr)
s.AddUnit(unit)
pb.RegisterSchedulerServer(gs, s)
go func() {
if err := gs.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}()
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
func TestRunJob(t *testing.T) {
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := pb.NewSchedulerClient(conn)
_, err = client.RunJob(ctx, &pb.RunJobRequest{UnitName: "niku"})
if err == nil {
t.Fatalf("RunJob should have returned an error for unit niku")
}
_, err = client.RunJob(ctx, &pb.RunJobRequest{UnitName: "incr"})
if err != nil {
t.Fatalf("RunJob call should have passed but got: %v", err)
}
time.Sleep(1 * time.Second)
if c.Current != 2 {
t.Fatalf("RunJob call `incr` should have incremented counter but stays at: %d", c.Current)
}
}

66
pkg/scheduler_unit.go Normal file
View File

@ -0,0 +1,66 @@
package gocron_server
import (
"os/exec"
"strings"
)
type UnitExecutable interface {
Call() (string, error)
}
type UnitExecCmd struct {
name string
args []string
}
func (ue *UnitExecCmd) Call() (string, error) {
cmd := exec.Command(ue.name, ue.args...)
out, err := cmd.Output()
strout := string(out[:])
if err != nil {
return strout, err
}
return strout, nil
}
func NewUnitExecCmd(command string) *UnitExecCmd {
cmdFrags := strings.Split(command, " ")
return &UnitExecCmd{name: cmdFrags[0], args: cmdFrags[1:]}
}
type UnitExecFn struct {
fn JobFunc
}
func (ue *UnitExecFn) Call() (string, error) {
return ue.fn()
}
func NewUnitExecFn(fn JobFunc) *UnitExecFn {
return &UnitExecFn{fn}
}
type SchedulerUnit struct {
Name string
Exec UnitExecutable
Cron string
}
func NewSchedulerUnit(name, cron string, exec UnitExecutable) *SchedulerUnit {
return &SchedulerUnit{
Name: name,
Exec: exec,
Cron: cron,
}
}
func NewManualUnit(name string, exec UnitExecutable) *SchedulerUnit {
return &SchedulerUnit{
Name: name,
Exec: exec,
Cron: "",
}
}

View File

@ -0,0 +1,32 @@
package gocron_server_test
import (
"testing"
gocron_server "github.com/strnophix/gocron-server/pkg"
)
func TestUnitExecutableCommand(t *testing.T) {
cmd := gocron_server.NewUnitExecCmd("echo hi")
out, err := cmd.Call()
if err != nil {
t.Fatalf("Should not have errored on a simple echo")
}
if out != "hi\n" {
t.Fatalf("Execution should have returned \"hi\" but gave: %s", out)
}
c := Counter{Current: 1}
fn := gocron_server.NewUnitExecFn(c.Increment)
out, _ = fn.Call()
if out != "2" {
t.Fatalf("Execution of unit should have incremented counter but got: %d", c.Current)
}
cmd = gocron_server.NewUnitExecCmd("false")
_, err = cmd.Call()
if err == nil {
t.Fatalf("Should have returned an error on a non-zero exit")
}
}

12
pkg/setup_test.go Normal file
View File

@ -0,0 +1,12 @@
package gocron_server_test
import "fmt"
type Counter struct {
Current int
}
func (c *Counter) Increment() (string, error) {
c.Current += 1
return fmt.Sprint(c.Current), nil
}

2
scripts/listen_jobs.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
grpcurl --plaintext -d "{}" localhost:9092 gocron_server.Scheduler/ListenJobs

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
NEW_EPOCH=$(($(date +%s) + 5))
grpcurl --plaintext -d "{\"UnitName\": \"incr\", \"RunAt\": $NEW_EPOCH}" localhost:9092 gocron_server.Scheduler/RunJob | jq

2
scripts/send_notify.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
grpcurl --plaintext -d "{\"UnitName\": \"$1\"}" localhost:9092 gocron_server.Scheduler/RunJob | jq