From 89c1c72d8722bf608344a27c08b1dee25c7ee98e Mon Sep 17 00:00:00 2001 From: kjuulh Date: Wed, 9 Aug 2023 15:44:31 +0200 Subject: [PATCH 1/2] feat(github): add github support Signed-off-by: kjuulh --- cmd/contractor/main.go | 37 ++++++- go.mod | 9 ++ go.sum | 78 +++++++++++++- internal/bot/giteabot.go | 17 ++- internal/features/handle_gitea_events.go | 16 +-- internal/features/handle_gitea_webhook.go | 3 +- internal/features/handle_github_events.go | 84 +++++++++++++++ internal/features/handle_github_webhook.go | 76 +++++++++++++ internal/models/requests.go | 37 ++++++- internal/providers/gitea.go | 2 +- internal/providers/github.go | 117 +++++++++++++++++++++ 11 files changed, 452 insertions(+), 24 deletions(-) create mode 100644 internal/features/handle_github_events.go create mode 100644 internal/features/handle_github_webhook.go create mode 100644 internal/providers/github.go diff --git a/cmd/contractor/main.go b/cmd/contractor/main.go index 792a690..918bb77 100644 --- a/cmd/contractor/main.go +++ b/cmd/contractor/main.go @@ -50,16 +50,23 @@ func serverCmd() *cobra.Command { var ( url string token string + + githubAppID int64 + githubInstallationID int64 + githubPrivateKeyPath string ) giteaClient := providers.NewGiteaClient(&url, &token) + githubClient := providers.NewGitHubClient(&githubAppID, &githubInstallationID, &githubPrivateKeyPath) renovateClient := renovate.NewRenovateClient("") queue := queue.NewGoQueue() - botHandler := bot.NewBotHandler(giteaClient) + botHandler := bot.NewBotHandler(giteaClient, githubClient) giteaWebhook := features.NewGiteaWebhook(botHandler, queue) + githubWebhook := features.NewGitHubWebhook(botHandler, queue) features.RegisterGiteaQueues(queue, renovateClient, giteaClient) + features.RegisterGitHubQueues(queue, renovateClient, githubClient) cmd := &cobra.Command{ Use: "server", @@ -68,7 +75,11 @@ func serverCmd() *cobra.Command { cmd.PersistentFlags().StringVar(&url, "url", "", "the api url of the server") cmd.PersistentFlags().StringVar(&token, "token", "", "the token to authenticate with") - cmd.AddCommand(serverServeCmd(&url, &token, giteaWebhook)) + cmd.PersistentFlags().Int64Var(&githubAppID, "github-app-id", 0, "github app id to authenticate with") + cmd.PersistentFlags().Int64Var(&githubInstallationID, "github-installation-id", 0, "github installation id to authenticate with") + cmd.PersistentFlags().StringVar(&githubPrivateKeyPath, "github-private-key-path", "", "path to the github app private key") + + cmd.AddCommand(serverServeCmd(&url, &token, giteaWebhook, githubWebhook)) return cmd } @@ -77,16 +88,34 @@ func serverServeCmd( url *string, token *string, giteaWebhook *features.GiteaWebhook, + githubWebhook *features.GitHubWebhook, ) *cobra.Command { cmd := &cobra.Command{ Use: "serve", Run: func(cmd *cobra.Command, args []string) { engine := gin.Default() + github := engine.Group("/github") + { + github.POST("/webhook", func(ctx *gin.Context) { + var request features.GitHubWebhookRequest + if err := ctx.BindJSON(&request); err != nil { + ctx.AbortWithError(500, err) + return + } + + if err := githubWebhook.HandleGitHubWebhook(ctx.Request.Context(), &request); err != nil { + ctx.AbortWithError(500, err) + return + } + + ctx.Status(204) + }) + } + gitea := engine.Group("/gitea") { gitea.POST("/webhook", func(ctx *gin.Context) { - var request features.GiteaWebhookRequest if err := ctx.BindJSON(&request); err != nil { ctx.AbortWithError(500, err) @@ -102,7 +131,7 @@ func serverServeCmd( }) } - engine.Run("0.0.0.0:8080") + engine.Run("0.0.0.0:9111") }, } diff --git a/go.mod b/go.mod index eb76840..5f9fd8e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( dagger.io/dagger v0.8.1 github.com/gin-gonic/gin v1.9.1 + github.com/google/go-github/v53 v53.2.0 github.com/google/uuid v1.3.0 github.com/joho/godotenv v1.5.1 github.com/spf13/cobra v1.7.0 @@ -13,15 +14,21 @@ require ( require ( github.com/99designs/gqlgen v0.17.31 // indirect github.com/Khan/genqlient v0.6.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/adrg/xdg v0.4.0 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -39,10 +46,12 @@ require ( golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect golang.org/x/tools v0.11.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4a41eac..e78dc17 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,12 @@ +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= dagger.io/dagger v0.8.1 h1:jLNPGubxrLWUfsX+snjaw913B1lxVmWftzdVehB+RQU= dagger.io/dagger v0.8.1/go.mod h1:CZwYt0FfVsEEYTFytzf2ihESB2P4H1S3/UfnrVxjBsE= github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -11,12 +14,19 @@ github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVb github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk= +github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -37,9 +47,21 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= +github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -97,29 +119,81 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/bot/giteabot.go b/internal/bot/giteabot.go index 27ab20f..ea43e85 100644 --- a/internal/bot/giteabot.go +++ b/internal/bot/giteabot.go @@ -10,11 +10,12 @@ import ( ) type BotHandler struct { - giteaClient *providers.GiteaClient + giteaClient *providers.GiteaClient + githubClient *providers.GitHubClient } -func NewBotHandler(gitea *providers.GiteaClient) *BotHandler { - return &BotHandler{giteaClient: gitea} +func NewBotHandler(gitea *providers.GiteaClient, github *providers.GitHubClient) *BotHandler { + return &BotHandler{giteaClient: gitea, githubClient: github} } func (b *BotHandler) Handle(input string) (output string, err error) { @@ -62,6 +63,14 @@ func (b *BotHandler) AppendComment( repository string, pullRequest int, comment string, + backend models.SupportedBackend, ) (*models.AddCommentResponse, error) { - return b.giteaClient.AddComment(owner, repository, pullRequest, comment) + switch backend { + case models.SupportedBackendGitHub: + return b.githubClient.AddComment(owner, repository, pullRequest, comment) + case models.SupportedBackendGitea: + return b.giteaClient.AddComment(owner, repository, pullRequest, comment) + default: + panic("backend chosen was not a valid option") + } } diff --git a/internal/features/handle_gitea_events.go b/internal/features/handle_gitea_events.go index 1c312bf..c9b3567 100644 --- a/internal/features/handle_gitea_events.go +++ b/internal/features/handle_gitea_events.go @@ -14,23 +14,23 @@ import ( func RegisterGiteaQueues(goqueue *queue.GoQueue, renovate *renovate.RenovateClient, giteaClient *providers.GiteaClient) { goqueue.Subscribe( - models.MessageTypeRefreshRepository, + models.MessageTypeRefreshGiteaRepository, func(ctx context.Context, item *queue.QueueMessage) error { log.Printf("handling message: %s, content: %s", item.Type, item.Content) return nil }, ) goqueue.Subscribe( - models.MessageTypeRefreshRepositoryDone, + models.MessageTypeRefreshGiteaRepositoryDone, func(ctx context.Context, item *queue.QueueMessage) error { log.Printf("handling message: %s, content: %s", item.Type, item.Content) return nil }, ) goqueue.Subscribe( - models.MessageTypeRefreshRepository, + models.MessageTypeRefreshGiteaRepository, func(ctx context.Context, item *queue.QueueMessage) error { - var request models.RefreshRepositoryRequest + var request models.RefreshGiteaRepositoryRequest if err := json.Unmarshal([]byte(item.Content), &request); err != nil { log.Printf("failed to unmarshal request body: %s", err.Error()) return err @@ -40,7 +40,7 @@ func RegisterGiteaQueues(goqueue *queue.GoQueue, renovate *renovate.RenovateClie defer cancel() if err := renovate.RefreshRepository(cancelCtx, request.Owner, request.Repository); err != nil { - goqueue.Insert(models.MessageTypeRefreshRepositoryDone, models.RefreshDoneRepositoryRequest{ + goqueue.Insert(models.MessageTypeRefreshGiteaRepositoryDone, models.RefreshGiteaRepositoryDoneRequest{ Repository: request.Repository, Owner: request.Owner, PullRequestID: request.PullRequestID, @@ -54,7 +54,7 @@ func RegisterGiteaQueues(goqueue *queue.GoQueue, renovate *renovate.RenovateClie return err } - goqueue.Insert(models.MessageTypeRefreshRepositoryDone, models.RefreshDoneRepositoryRequest{ + goqueue.Insert(models.MessageTypeRefreshGiteaRepositoryDone, models.RefreshGiteaRepositoryDoneRequest{ Repository: request.Repository, Owner: request.Owner, PullRequestID: request.PullRequestID, @@ -70,9 +70,9 @@ func RegisterGiteaQueues(goqueue *queue.GoQueue, renovate *renovate.RenovateClie ) goqueue.Subscribe( - models.MessageTypeRefreshRepositoryDone, + models.MessageTypeRefreshGiteaRepositoryDone, func(ctx context.Context, item *queue.QueueMessage) error { - var doneRequest models.RefreshDoneRepositoryRequest + var doneRequest models.RefreshGiteaRepositoryDoneRequest if err := json.Unmarshal([]byte(item.Content), &doneRequest); err != nil { log.Printf("failed to unmarshal request body: %s", err.Error()) return err diff --git a/internal/features/handle_gitea_webhook.go b/internal/features/handle_gitea_webhook.go index d7a7490..48eaf9e 100644 --- a/internal/features/handle_gitea_webhook.go +++ b/internal/features/handle_gitea_webhook.go @@ -54,12 +54,13 @@ func (gw *GiteaWebhook) HandleGiteaWebhook(ctx context.Context, request *GiteaWe parts[1], request.Issue.Number, output, + models.SupportedBackendGitea, ) if err != nil { return err } - if err := gw.queue.Insert(models.MessageTypeRefreshRepository, models.RefreshRepositoryRequest{ + if err := gw.queue.Insert(models.MessageTypeRefreshGiteaRepository, models.RefreshGiteaRepositoryRequest{ Repository: parts[1], Owner: parts[0], PullRequestID: request.Issue.Number, diff --git a/internal/features/handle_github_events.go b/internal/features/handle_github_events.go new file mode 100644 index 0000000..8c8bea0 --- /dev/null +++ b/internal/features/handle_github_events.go @@ -0,0 +1,84 @@ +package features + +import ( + "context" + "encoding/json" + "log" + "time" + + "git.front.kjuulh.io/kjuulh/contractor/internal/models" + "git.front.kjuulh.io/kjuulh/contractor/internal/providers" + "git.front.kjuulh.io/kjuulh/contractor/internal/queue" + "git.front.kjuulh.io/kjuulh/contractor/internal/renovate" +) + +func RegisterGitHubQueues(goqueue *queue.GoQueue, renovate *renovate.RenovateClient, giteaClient *providers.GitHubClient) { + goqueue.Subscribe( + models.MessageTypeRefreshGitHubRepository, + func(ctx context.Context, item *queue.QueueMessage) error { + log.Printf("handling message: %s, content: %s", item.Type, item.Content) + return nil + }, + ) + goqueue.Subscribe( + models.MessageTypeRefreshGitHubRepositoryDone, + func(ctx context.Context, item *queue.QueueMessage) error { + log.Printf("handling message: %s, content: %s", item.Type, item.Content) + return nil + }, + ) + goqueue.Subscribe( + models.MessageTypeRefreshGitHubRepository, + func(ctx context.Context, item *queue.QueueMessage) error { + var request models.RefreshGitHubRepositoryRequest + if err := json.Unmarshal([]byte(item.Content), &request); err != nil { + log.Printf("failed to unmarshal request body: %s", err.Error()) + return err + } + + cancelCtx, cancel := context.WithTimeout(ctx, time.Minute*5) + defer cancel() + + if err := renovate.RefreshRepository(cancelCtx, request.Owner, request.Repository); err != nil { + goqueue.Insert(models.MessageTypeRefreshGitHubRepositoryDone, models.RefreshGitHubRepositoryDoneRequest{ + Repository: request.Repository, + Owner: request.Owner, + PullRequestID: request.PullRequestID, + CommentID: request.CommentID, + CommentBody: request.CommentBody, + ReportProgress: request.ReportProgress, + Status: "failed", + Error: err.Error(), + }) + + return err + } + + goqueue.Insert(models.MessageTypeRefreshGitHubRepositoryDone, models.RefreshGitHubRepositoryDoneRequest{ + Repository: request.Repository, + Owner: request.Owner, + PullRequestID: request.PullRequestID, + CommentID: request.CommentID, + CommentBody: request.CommentBody, + ReportProgress: request.ReportProgress, + Status: "done", + Error: "", + }) + + return nil + }, + ) + + goqueue.Subscribe( + models.MessageTypeRefreshGitHubRepositoryDone, + func(ctx context.Context, item *queue.QueueMessage) error { + var doneRequest models.RefreshGitHubRepositoryDoneRequest + if err := json.Unmarshal([]byte(item.Content), &doneRequest); err != nil { + log.Printf("failed to unmarshal request body: %s", err.Error()) + return err + } + + return giteaClient.EditComment(ctx, &doneRequest) + }, + ) +} diff --git a/internal/features/handle_github_webhook.go b/internal/features/handle_github_webhook.go new file mode 100644 index 0000000..42fab83 --- /dev/null +++ b/internal/features/handle_github_webhook.go @@ -0,0 +1,76 @@ +package features + +import ( + "context" + "log" + "strings" + + "git.front.kjuulh.io/kjuulh/contractor/internal/bot" + "git.front.kjuulh.io/kjuulh/contractor/internal/models" + "git.front.kjuulh.io/kjuulh/contractor/internal/queue" +) + +type GitHubWebhook struct { + botHandler *bot.BotHandler + queue *queue.GoQueue +} + +type GitHubWebhookRequest struct { + Action string `json:"action"` + Issue struct { + Id int `json:"id"` + Number int `json:"number"` + } `json:"issue"` + Comment struct { + Body string `json:"body"` + } `json:"comment"` + Repository struct { + FullName string `json:"full_name"` + } +} + +func NewGitHubWebhook(botHandler *bot.BotHandler, queue *queue.GoQueue) *GitHubWebhook { + return &GitHubWebhook{ + botHandler: botHandler, + queue: queue, + } +} + +func (gw *GitHubWebhook) HandleGitHubWebhook(ctx context.Context, request *GitHubWebhookRequest) error { + command, ok := validateBotComment(request.Comment.Body) + if ok { + log.Printf("got webhook request: contractor %s", command) + + bot := gw.botHandler + output, err := bot.Handle(command) + if err != nil { + log.Printf("failed to run bot handler with error: %s", err.Error()) + } + + parts := strings.Split(request.Repository.FullName, "/") + + comment, err := bot.AppendComment( + parts[0], + parts[1], + request.Issue.Number, + output, + models.SupportedBackendGitHub, + ) + if err != nil { + return err + } + + if err := gw.queue.Insert(models.MessageTypeRefreshGitHubRepository, models.RefreshGitHubRepositoryRequest{ + Repository: parts[1], + Owner: parts[0], + PullRequestID: request.Issue.Number, + CommentID: comment.ID, + CommentBody: comment.Body, + ReportProgress: true, + }); err != nil { + return err + } + } + + return nil +} diff --git a/internal/models/requests.go b/internal/models/requests.go index 392aed0..6716c8a 100644 --- a/internal/models/requests.go +++ b/internal/models/requests.go @@ -1,8 +1,10 @@ package models const ( - MessageTypeRefreshRepository = "refresh_repository" - MessageTypeRefreshRepositoryDone = "refresh_repository_done" + MessageTypeRefreshGiteaRepository = "refresh_gitea_repository" + MessageTypeRefreshGiteaRepositoryDone = "refresh_gitea_repository_done" + MessageTypeRefreshGitHubRepository = "refresh_github_repository" + MessageTypeRefreshGitHubRepositoryDone = "refresh_github_repository_done" ) type CreateHook struct { @@ -14,7 +16,7 @@ type CreateHook struct { Type string `json:"type"` } -type RefreshRepositoryRequest struct { +type RefreshGiteaRepositoryRequest struct { Repository string `json:"repository"` Owner string `json:"owner"` PullRequestID int `json:"pullRequestId"` @@ -23,7 +25,27 @@ type RefreshRepositoryRequest struct { ReportProgress bool `json:"reportProgress"` } -type RefreshDoneRepositoryRequest struct { +type RefreshGiteaRepositoryDoneRequest struct { + Repository string `json:"repository"` + Owner string `json:"owner"` + PullRequestID int `json:"pullRequestId"` + CommentID int `json:"commentId"` + CommentBody string `json:"commentBody"` + ReportProgress bool `json:"reportProgress"` + Status string `json:"status"` + Error string `json:"error"` +} + +type RefreshGitHubRepositoryRequest struct { + Repository string `json:"repository"` + Owner string `json:"owner"` + PullRequestID int `json:"pullRequestId"` + CommentID int `json:"commentId"` + CommentBody string `json:"commentBody"` + ReportProgress bool `json:"reportProgress"` +} + +type RefreshGitHubRepositoryDoneRequest struct { Repository string `json:"repository"` Owner string `json:"owner"` PullRequestID int `json:"pullRequestId"` @@ -38,3 +60,10 @@ type AddCommentResponse struct { Body string `json:"body"` ID int `json:"id"` } + +type SupportedBackend string + +const ( + SupportedBackendGitHub SupportedBackend = "github" + SupportedBackendGitea SupportedBackend = "gitea" +) diff --git a/internal/providers/gitea.go b/internal/providers/gitea.go index 2851198..3463b17 100644 --- a/internal/providers/gitea.go +++ b/internal/providers/gitea.go @@ -31,7 +31,7 @@ func NewGiteaClient(url, token *string) *GiteaClient { func (gc *GiteaClient) EditComment( ctx context.Context, - doneRequest *models.RefreshDoneRepositoryRequest, + doneRequest *models.RefreshGiteaRepositoryDoneRequest, ) error { commentBody := html.UnescapeString(doneRequest.CommentBody) startCmnt := "" diff --git a/internal/providers/github.go b/internal/providers/github.go new file mode 100644 index 0000000..217846d --- /dev/null +++ b/internal/providers/github.go @@ -0,0 +1,117 @@ +package providers + +import ( + "context" + "fmt" + "html" + "log" + "net/http" + "strings" + + "git.front.kjuulh.io/kjuulh/contractor/internal/models" + + "github.com/bradleyfalzon/ghinstallation/v2" + "github.com/google/go-github/v53/github" +) + +type GitHubClient struct { + appID *int64 + installationID *int64 + privateKeyPath *string + + client *github.Client +} + +func NewGitHubClient(appID, installationID *int64, privateKeyPath *string) *GitHubClient { + return &GitHubClient{ + appID: appID, + installationID: installationID, + privateKeyPath: privateKeyPath, + } +} + +func (gc *GitHubClient) makeSureClientExist() { + if gc.client != nil { + return + } + + tr := http.DefaultTransport + itr, err := ghinstallation.NewKeyFromFile(tr, *gc.appID, *gc.installationID, *gc.privateKeyPath) + if err != nil { + log.Fatal(err) + } + + client := github.NewClient(&http.Client{Transport: itr}) + + gc.client = client +} + +func (gc *GitHubClient) EditComment( + ctx context.Context, + doneRequest *models.RefreshGitHubRepositoryDoneRequest, +) error { + gc.makeSureClientExist() + + commentBody := html.UnescapeString(doneRequest.CommentBody) + startCmnt := "" + startIdx := strings.Index(commentBody, startCmnt) + endIdx := strings.Index(commentBody, "") + if startIdx >= 0 && endIdx >= 0 { + log.Println("found comment to replace") + + var content string + + if doneRequest.Error != "" { + content = fmt.Sprintf("
ERROR: %s

", doneRequest.Error) + } + if doneRequest.Status != "" { + content = fmt.Sprintf("%s

%s

", content, doneRequest.Status) + } + + doneRequest.CommentBody = fmt.Sprintf( + "%s

%s

%s", + commentBody[:startIdx+len(startCmnt)], + content, + commentBody[endIdx:], + ) + } + + _, _, err := gc.client.Issues.EditComment(ctx, doneRequest.Owner, doneRequest.Repository, int64(doneRequest.CommentID), &github.IssueComment{ + Body: &doneRequest.CommentBody, + }) + if err != nil { + log.Printf("failed to update comment: %s", err.Error()) + return err + } + + return nil +} + +func (gc *GitHubClient) CreateWebhook(owner, repository string) error { + gc.makeSureClientExist() + + // TODO: support for personal access tokens + // We implicitly get support via. github apps + + return nil +} + +func (gc *GitHubClient) AddComment( + owner, repository string, + pullRequest int, + comment string, +) (*models.AddCommentResponse, error) { + gc.makeSureClientExist() + + resp, _, err := gc.client.Issues.CreateComment(context.Background(), owner, repository, pullRequest, &github.IssueComment{ + Body: &comment, + }) + if err != nil { + return nil, err + } + + return &models.AddCommentResponse{ + Body: *resp.Body, + ID: int(*resp.ID), + }, nil +} -- 2.45.2 From 8f924e5dddd0f4449a68b501aebf12de98a593a3 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Wed, 9 Aug 2023 15:46:52 +0200 Subject: [PATCH 2/2] docs(README): update with github support Signed-off-by: kjuulh --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e104758..cc9d973 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,13 @@ what the project will look like once feature-complete. - Includes basic setup such as working server bot, and installation command, automation is missing however. Also only gitea support for now, because this is where the project initially is supposed to be in use. -- [ ] 0.2.0 - - Add GitHub support +- [x] 0.2.0 + - Add GitHub support, only github app support for now. This means that install is not needed, because a github app will automatically receive webhooks if setup properly. docs are missing for this (tbd). - [ ] 0.3.0 - Add Delegation support (not clustering, just delegation of renovate jobs) - [ ] 0.4.0 - Slack integration - [ ] 0.5.0 - - GitHub App and such support -- [ ] 0.6.0 - Add api key support ## Getting started -- 2.45.2