From 38f443846b116cbb03a8883bc8a7d95630675665 Mon Sep 17 00:00:00 2001 From: jspc Date: Sat, 17 Sep 2022 13:33:05 +0100 Subject: [PATCH 1/4] Autogenerate auto responses The only reason to have this seperate file, rather than storing this in source, is so these things can be edited and maintained by people who feel less confident editing source code. --- .github/workflows/ci.yml | 4 +-- Makefile | 7 +++-- config.gen.go | 13 +++++++++ config.toml | 28 ++++++++++++++++++++ gen/main.go | 57 ++++++++++++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 +++ main.go | 23 ---------------- 8 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 config.gen.go create mode 100644 config.toml create mode 100644 gen/main.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce6e160..f01e2d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: build-and-push: needs: sonarcloud runs-on: ubuntu-latest - if: contains(github.ref, 'refs/tags') + #if: contains(github.ref, 'refs/tags') steps: - uses: actions/checkout@v3 @@ -102,7 +102,7 @@ jobs: with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: bom.json - asset_name: bon.json + asset_name: bom.json tag: ${{ github.ref }} overwrite: true body: "CycloneDX json output" diff --git a/Makefile b/Makefile index 5b7b14c..609c76a 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,16 @@ COSIGN_SECRET ?= cosign.key default: app -app: *.go go.mod go.sum +app: *.go go.mod go.sum config.gen.go go build -o $@ -ldflags="-s -w -linkmode=external -X main.LogLevel=$(LOGLVL)" -buildmode=pie -trimpath upx $@ +config.gen.go: config.toml gen/main.go + go run gen/main.go -f $< > $@ + .PHONY: docker-build docker-push docker-build: - docker build --build-arg logLevel=$(LOGLVL) -t $(IMG):$(TAG) . + docker build --label "tag=$(TAG)" --label "bom=https://github.com/gender-equality-community/gec-bot/releases/download/$(TAG)/bom.json" --build-arg logLevel=$(LOGLVL) -t $(IMG):$(TAG) . docker-push: docker push $(IMG):$(TAG) diff --git a/config.gen.go b/config.gen.go new file mode 100644 index 0000000..b469d56 --- /dev/null +++ b/config.gen.go @@ -0,0 +1,13 @@ +// Code generated from gen/main.go DO NOT EDIT + +package main + +// Greeting response is sent when a recipient sends a message sends us a greeting +const greetingResponse = "Hello, and welcome to the Anonymous GEC Advisor. What's on your mind?" + +// Thank You response is sent when a recipient sends us a message and is capped at a max of 1 per 30 mins +const thankyouResponse = "Thank you for your message, please provide as much information as you're comfortable sharing and we'll get back to you as soon as we can." + +// Disclaimer response is sent to ensure recipients don't send us stuff we can't deal with. +const disclaimerResponse = "DISCLAIMER: This is not an incident reporting service. If you believe you're being subjected to bullying, harassment, or misconduct then we cannot escalate on your behalf but we can advise you on your next steps." + diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..51bdd7c --- /dev/null +++ b/config.toml @@ -0,0 +1,28 @@ +# This file is used to generate the file `config.gen.go`; we compile these phrases into the binary +# (rather than allowing for a separate runtime configuration options) purely because it makes +# distribution and start up a little easier for us. +# +# The only reason to have this seperate file, rather than storing this in source, is so these +# things can be edited and maintained by people who feel less confident editing source code. + +[responses] +# Greeting Response is sent when a recipient sends a message that looks vaguely like +# a greeting. +# +# To understand what this might look like, take a look in phrases.go +greeting = "Hello, and welcome to the Anonymous GEC Advisor. What's on your mind?" + +# Thank You response is sent when a recipient sends us a message. +# +# To keep this from spamming the hell out of people, we only send a maximum of 1 +# response per 30 minutes. +# +# Essentially, when a message comes in, we check whether we've responded in the last +# 30 minutes and if we haven't then we send another +thankyou = "Thank you for your message, please provide as much information as you're comfortable sharing and we'll get back to you as soon as we can." + +# Disclaimer response is sent to ensure recipients don't send us things we can't, or aren't allowed to, deal with. +# +# In much the same way as we only send a Thank You every 30 mins or less, we only send the +# disclaimer once every 24 hours. +disclaimer = "DISCLAIMER: This is not an incident reporting service. If you believe you're being subjected to bullying, harassment, or misconduct then we cannot escalate on your behalf but we can advise you on your next steps." diff --git a/gen/main.go b/gen/main.go new file mode 100644 index 0000000..12284ad --- /dev/null +++ b/gen/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/BurntSushi/toml" + "github.com/dave/jennifer/jen" +) + +const output = "config.gen.go" + +var config = flag.String("f", "config.toml", "configuration file to load") + +// Config holds the loaded configuration from our input file +type Config struct { + Responses struct { + Greeting string + Thankyou string + Disclaimer string + } +} + +func main() { + flag.Parse() + + cf, err := os.ReadFile(*config) + if err != nil { + panic(err) + } + + c := new(Config) + + _, err = toml.Decode(string(cf), c) + if err != nil { + panic(err) + } + + f := jen.NewFile("main") + f.HeaderComment("Code generated from gen/main.go DO NOT EDIT ") + + f.Comment("Greeting response is sent when a recipient sends a message sends us a greeting") + f.Const().Id("greetingResponse").Op("=").Lit(c.Responses.Greeting) + + f.Comment("Thank You response is sent when a recipient sends us a message and is capped at a max of 1 per 30 mins") + f.Const().Id("thankyouResponse").Op("=").Lit(c.Responses.Thankyou) + + f.Comment("Disclaimer response is sent to ensure recipients don't send us stuff we can't deal with.") + f.Const().Id("disclaimerResponse").Op("=").Lit(c.Responses.Disclaimer) + + buf := strings.Builder{} + f.Render(&buf) + + fmt.Println(buf.String()) +} diff --git a/go.mod b/go.mod index 469c0f4..43105b0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/gender-equality-community/gec-bot go 1.17 require ( + github.com/BurntSushi/toml v1.2.0 github.com/agnivade/levenshtein v1.1.1 + github.com/dave/jennifer v1.5.1 github.com/go-redis/redis/v9 v9.0.0-beta.2 github.com/mattn/go-sqlite3 v1.14.15 github.com/mdp/qrterminal v1.0.1 diff --git a/go.sum b/go.sum index b2cff95..16600c8 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,14 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dave/jennifer v1.5.1 h1:AI8gaM02nCYRw6/WTH0W+S6UNck9YqPZ05xoIxQtuoE= +github.com/dave/jennifer v1.5.1/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= diff --git a/main.go b/main.go index 577b53c..b7a637b 100644 --- a/main.go +++ b/main.go @@ -11,29 +11,6 @@ import ( waLog "go.mau.fi/whatsmeow/util/log" ) -const ( - // Greeting Response is sent when a recipient sends a message that looks vaguely like - // a greeting. - // - // To understand what this might look like, take a look in phrases.go - greetingResponse = "Hello, and welcome to the Anonymous GEC Advisor. What's on your mind?" - - // Thank You response is sent when a recipient sends us a message. - // - // To keep this from spamming the hell out of people, we only send a maximum of 1 - // response per 30 minutes. - // - // Essentially, when a message comes in, we check whether we've responded in the last - // 30 minutes and if we haven't then we send another - thankyouResponse = "Thank you for your message, please provide as much information as you're comfortable sharing and we'll get back to you as soon as we can." - - // Disclaimer response is sent to ensure recipients don't send us shit we can't deal with. - // - // In much the same way as we only send a Thank You every 30 mins or less, we only send the - // disclaimer once every 24 hours. - disclaimerResponse = "DISCLAIMER: This is not an incident reporting service. If you believe you're being subjected to bullying, harassment, or misconduct then we cannot escalate on your behalf but we can advise you on your next steps." -) - var ( LogLevel = "DEBUG" db = os.Getenv("DATABASE") From dc87e3c2f2d8536f0a7f0a99c870b48c769ebbc6 Mon Sep 17 00:00:00 2001 From: jspc Date: Sat, 17 Sep 2022 13:38:36 +0100 Subject: [PATCH 2/4] Ensure rendering errors are caught and actioned --- gen/main.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gen/main.go b/gen/main.go index 12284ad..cc5da9c 100644 --- a/gen/main.go +++ b/gen/main.go @@ -51,7 +51,11 @@ func main() { f.Const().Id("disclaimerResponse").Op("=").Lit(c.Responses.Disclaimer) buf := strings.Builder{} - f.Render(&buf) + + err = f.Render(&buf) + if err != nil { + panic(err) + } fmt.Println(buf.String()) } From 7df9562c66d7657da4bb8cc7ec5009787cffdfb8 Mon Sep 17 00:00:00 2001 From: jspc Date: Sat, 17 Sep 2022 13:52:18 +0100 Subject: [PATCH 3/4] Re-disable bulding on non-tags --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f01e2d6..a800a61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: build-and-push: needs: sonarcloud runs-on: ubuntu-latest - #if: contains(github.ref, 'refs/tags') + if: contains(github.ref, 'refs/tags') steps: - uses: actions/checkout@v3 From 4f227c00d82acada280b22c88d43f8f4cdb83f11 Mon Sep 17 00:00:00 2001 From: jspc Date: Sat, 17 Sep 2022 14:17:01 +0100 Subject: [PATCH 4/4] Ensure code generation is tested --- gen/main.go | 41 ++++++++++++++------- gen/main_test.go | 79 ++++++++++++++++++++++++++++++++++++++++ gen/testdata/config.toml | 4 ++ gen/testdata/empty.toml | 0 go.mod | 1 + go.sum | 2 + 6 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 gen/main_test.go create mode 100644 gen/testdata/config.toml create mode 100644 gen/testdata/empty.toml diff --git a/gen/main.go b/gen/main.go index cc5da9c..8280884 100644 --- a/gen/main.go +++ b/gen/main.go @@ -14,30 +14,47 @@ const output = "config.gen.go" var config = flag.String("f", "config.toml", "configuration file to load") +// Responses holds our default responses +type Responses struct { + Greeting string + Thankyou string + Disclaimer string +} + // Config holds the loaded configuration from our input file type Config struct { - Responses struct { - Greeting string - Thankyou string - Disclaimer string - } + Responses Responses } func main() { flag.Parse() - cf, err := os.ReadFile(*config) + c, err := readToml(*config) if err != nil { panic(err) } - c := new(Config) - - _, err = toml.Decode(string(cf), c) + f, err := generate(c) if err != nil { panic(err) } + fmt.Println(f) +} + +func readToml(f string) (c Config, err error) { + //#nosec + cf, err := os.ReadFile(f) + if err != nil { + return + } + + _, err = toml.Decode(string(cf), &c) + + return +} + +func generate(c Config) (out string, err error) { f := jen.NewFile("main") f.HeaderComment("Code generated from gen/main.go DO NOT EDIT ") @@ -53,9 +70,7 @@ func main() { buf := strings.Builder{} err = f.Render(&buf) - if err != nil { - panic(err) - } + out = buf.String() - fmt.Println(buf.String()) + return } diff --git a/gen/main_test.go b/gen/main_test.go new file mode 100644 index 0000000..3a88741 --- /dev/null +++ b/gen/main_test.go @@ -0,0 +1,79 @@ +package main + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var ( + happyConfig = Config{ + Responses: Responses{ + Greeting: "Hi there!", + Thankyou: "Thanks!", + Disclaimer: "We can't do that!", + }, + } +) + +func TestReadToml(t *testing.T) { + for _, test := range []struct { + name string + fn string + expect Config + expectError bool + }{ + {"Config file does not exist", "testdata/nonsuch.toml", Config{}, true}, + {"Config file exists but is empty", "testdata/empty.toml", Config{}, false}, + {"Config file exists and has content", "testdata/config.toml", happyConfig, false}, + } { + t.Run(test.name, func(t *testing.T) { + got, err := readToml(test.fn) + if err != nil && !test.expectError { + t.Errorf("unexpected error: %v", err) + } else if err == nil && test.expectError { + t.Error("expected error") + } + + if !reflect.DeepEqual(test.expect, got) { + t.Errorf("expected %#v, received %#v", test.expect, got) + } + }) + } +} + +func TestGenerate(t *testing.T) { + expect := `// Code generated from gen/main.go DO NOT EDIT + +package main + +// Greeting response is sent when a recipient sends a message sends us a greeting +const greetingResponse = "Hi there!" + +// Thank You response is sent when a recipient sends us a message and is capped at a max of 1 per 30 mins +const thankyouResponse = "Thanks!" + +// Disclaimer response is sent to ensure recipients don't send us stuff we can't deal with. +const disclaimerResponse = "We can't do that!" +` + got, err := generate(happyConfig) + if err != nil { + t.Errorf("expected error") + } + + if expect != got { + t.Errorf(cmp.Diff(expect, got)) + } +} + +func TestMain(t *testing.T) { + defer func() { + err := recover() + if err == nil { + t.Error("expected error") + } + }() + + main() +} diff --git a/gen/testdata/config.toml b/gen/testdata/config.toml new file mode 100644 index 0000000..3a83189 --- /dev/null +++ b/gen/testdata/config.toml @@ -0,0 +1,4 @@ +[responses] +greeting = "Hi there!" +thankyou = "Thanks!" +disclaimer = "We can't do that!" diff --git a/gen/testdata/empty.toml b/gen/testdata/empty.toml new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod index 43105b0..58a986e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/agnivade/levenshtein v1.1.1 github.com/dave/jennifer v1.5.1 github.com/go-redis/redis/v9 v9.0.0-beta.2 + github.com/google/go-cmp v0.5.8 github.com/mattn/go-sqlite3 v1.14.15 github.com/mdp/qrterminal v1.0.1 github.com/rs/xid v1.4.0 diff --git a/go.sum b/go.sum index 16600c8..b8ab6c8 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/go-redis/redis/v9 v9.0.0-beta.2 h1:ZSr84TsnQyKMAg8gnV+oawuQezeJR11/09 github.com/go-redis/redis/v9 v9.0.0-beta.2/go.mod h1:Bldcd/M/bm9HbnNPi/LUtYBSD8ttcZYBMupwMXhdU0o= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=