{"id":20208802,"url":"https://github.com/palantir/go-githubapp","last_synced_at":"2025-05-15T03:02:50.714Z","repository":{"id":33817257,"uuid":"151643520","full_name":"palantir/go-githubapp","owner":"palantir","description":"A simple Go framework for building GitHub Apps","archived":false,"fork":false,"pushed_at":"2025-05-05T13:38:03.000Z","size":601,"stargazers_count":366,"open_issues_count":11,"forks_count":60,"subscribers_count":228,"default_branch":"develop","last_synced_at":"2025-05-05T14:48:45.279Z","etag":null,"topics":["framework","github-app","golang","octo-correct-managed"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/palantir/go-githubapp","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/palantir.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-10-04T22:26:33.000Z","updated_at":"2025-05-05T13:38:06.000Z","dependencies_parsed_at":"2023-02-15T11:15:24.754Z","dependency_job_id":"b3969a52-2278-4337-9608-f4939c396bf3","html_url":"https://github.com/palantir/go-githubapp","commit_stats":null,"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palantir%2Fgo-githubapp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palantir%2Fgo-githubapp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palantir%2Fgo-githubapp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palantir%2Fgo-githubapp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/palantir","download_url":"https://codeload.github.com/palantir/go-githubapp/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264737,"owners_count":22041789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","github-app","golang","octo-correct-managed"],"created_at":"2024-11-14T05:37:07.939Z","updated_at":"2025-05-15T03:02:50.601Z","avatar_url":"https://github.com/palantir.png","language":"Go","readme":"# go-githubapp [![GoDoc](https://godoc.org/github.com/palantir/go-githubapp?status.svg)](http://godoc.org/github.com/palantir/go-githubapp)\n\nA library for building [GitHub Apps](https://developer.github.com/apps/) and\nother services that handle GitHub webhooks.\n\nThe library provides an `http.Handler` implementation that dispatches webhook\nevents to the correct place, removing boilerplate and letting you focus on the\nlogic of your application.\n\n* [Usage](#usage)\n  + [Examples](#examples)\n  + [Dependencies](#dependencies)\n* [Asynchronous Dispatch](#asynchronous-dispatch)\n* [Structured Logging](#structured-logging)\n* [GitHub Clients](#github-clients)\n* [Metrics](#metrics)\n* [Background Jobs and Multi-Organization Operations](#background-jobs-and-multi-organization-operations)\n* [Config Loading](#config-loading)\n* [OAuth2](#oauth2)\n* [Stability and Versioning Guarantees](#stability-and-versioning-guarantees)\n* [Contributing](#contributing)\n\n## Usage\n\nMost users will implement `githubapp.EventHandler` for each webhook\nevent that needs to be handled. A single implementation can also respond to\nmultiple event types if they require the same actions:\n\n```go\ntype CommentHandler struct {\n    githubapp.ClientCreator\n}\n\nfunc (h *CommentHandler) Handles() []string {\n    return []string{\"issue_comment\"}\n}\n\nfunc (h *CommentHandler) Handle(ctx context.Context, eventType, deliveryID string, payload []byte) error {\n    // from github.com/google/go-github/github\n    var event github.IssueCommentEvent\n    if err := json.Unmarshal(payload, \u0026event); err != nil {\n        return err\n    }\n\n    // do something with the content of the event\n}\n```\n\nWe recommend embedding `githubapp.ClientCreator` in handler implementations as\nan easy way to access GitHub clients.\n\nOnce you define handlers, register them with an event dispatcher and associate\nit with a route in any `net/http`-compatible HTTP router:\n\n```go\nfunc registerRoutes(c githubapp.Config) {\n    cc := githubapp.NewDefaultCachingClientCreator(c)\n\n    http.Handle(\"/api/github/hook\", githubapp.NewDefaultEventDispatcher(c,\n        \u0026CommentHandler{cc},\n        // ...\n    ))\n}\n```\n\nWe recommend using [go-baseapp](https://github.com/palantir/go-baseapp) as the minimal server\nframework for writing github apps, though go-githubapp works well with the standard library and \ncan be easily integrated into most existing frameworks.\n\n### Examples\n\nThe [example package](example/main.go) contains a fully functional server\nusing `go-githubapp`. The example app responds to comments on pull requests by\ncommenting with a copy of the comment body.\n\nTo run the app, update `example/config.yml` with appropriate secrets and then\nrun:\n\n    ./godelw run example\n\n### Dependencies\n\n`go-githubapp` has minimal dependencies, but does make some decisions:\n\n- [rs/zerolog](https://github.com/rs/zerolog) for logging\n- [rcrowley/go-metrics](https://github.com/rcrowley/go-metrics) for metrics\n- [google/go-github](https://github.com/google/go-github) for v3 (REST) API client\n- [shurcooL/githubv4](https://github.com/shurcooL/githubv4) for v4 (GraphQL) API client\n\nLogging and metrics are only active when they are configured (see below). This\nmeans you can add your own logging or metrics libraries without conflict, but\nwill miss out on the free built-in support.\n\n## Asynchronous Dispatch\n\nGitHub imposes timeouts on webhook delivery responses. If an application does\nnot respond in time, GitHub closes the connection and marks the delivery as\nfailed. `go-githubapp` optionally supports asynchronous dispatch to help solve\nthis problem. When enabled, the event dispatcher sends a response to GitHub after\nvalidating the payload and then runs the event handler in a separate goroutine.\n\nTo enable, select an appropriate _scheduler_ and configure the event dispatcher\nto use it:\n\n```go\ndispatcher := githubapp.NewEventDispatcher(handlers, secret, githubapp.WithScheduler(\n    githubapp.AsyncScheduler(),\n))\n```\n\nThe following schedulers are included in the library:\n\n- `DefaultScheduler` - a synchronous scheduler that runs event handlers in\n  the current goroutine. This is the default mode.\n\n- `AsyncScheduler` - an asynchronous scheduler that handles each event in a\n  new goroutine. This is the simplest asynchronous option.\n\n- `QueueAsyncScheduler` - an asynchronous scheduler that queues events and\n  handles them with a fixed pool of worker goroutines. This is useful to limit\n  the amount of concurrent work.\n\n`AsyncScheduler` and `QueueAsyncScheduler` support several additional options\nand customizations; see the documentation for details.\n\n## Structured Logging\n\n`go-githubapp` uses [rs/zerolog](https://github.com/rs/zerolog) for structured\nlogging. A logger must be stored in the `context.Context` associated with each\n`http.Request`:\n\n```\nfunc (d *EventDispatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    logger := zerolog.Ctx(r.Context())\n    ...\n}\n```\n\nIf there is no logger in the context, log output is disabled. It's expected\nthat HTTP middleware, like that provided by the [hlog package][], configures\nthe `http.Request` context automatically.\n\nBelow are the standard keys used when logging events. They are also exported as\nconstants.\n\n| exported constant | key | definition |\n| ----------------- | --- | ---------- |\n| `LogKeyEventType` | `github_event_type` | the [github event type header](https://developer.github.com/webhooks/#delivery-headers) |\n| `LogKeyDeliveryID` | `github_delivery_id` | the [github event delivery id header](https://developer.github.com/webhooks/#delivery-headers) |\n| `LogKeyInstallationID` | `github_installation_id` | the [installation id the app is authenticating with](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#accessing-api-endpoints-as-a-github-app) |\n| `LogKeyRepositoryName` | `github_repository_name` | the repository name of the pull request being acted on |\n| `LogKeyRepositoryOwner` | `github_repository_owner` | the repository owner of the pull request being acted on |\n| `LogKeyPRNum` | `github_pr_num` | the number of the pull request being acted on |\n\nWhere appropriate, the library creates derived loggers with the above keys set\nto the correct values.\n\n[hlog package]: https://github.com/rs/zerolog#integration-with-nethttp\n\n## GitHub Clients\n\nAuthenticated and configured GitHub clients can be retrieved from\n`githubapp.ClientCreator` implementations. The library provides a basic\nimplementation and a caching version.\n\nThere are three types of clients and two API versions for a total of six\ndistinct clients:\n\n- An _application_ client authenticates [as the application][] and can only\n  call limited APIs that mostly return application metadata.\n- An _installation_ client authenticates [as an installation][] and can call\n  any APIs where the has been installed and granted permissions.\n- A _token_ client authenticates with a static OAuth2 token associated with a\n  user account.\n\n[as the application]: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-a-github-app\n[as an installation]: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation\n\n`go-githubapp` also exposes various configuration options for GitHub clients.\nThese are provided when calling `githubapp.NewClientCreator`:\n\n- `githubapp.WithClientUserAgent` sets a `User-Agent` string for all clients\n- `githubapp.WithClientTimeout` sets a timeout for requests made by all clients\n- `githubapp.WithClientCaching` enables response caching for all v3 (REST) clients.\n   The cache can be configured to always validate responses or to respect\n   the cache headers returned by GitHub. Re-validation is useful if data\n   often changes faster than the requested cache duration.\n- `githubapp.WithClientMiddleware` allows customization of the\n  `http.RoundTripper` used by all clients and is useful if you want to log\n  requests or emit metrics about GitHub requests and responses.\n\nThe library provides the following middleware:\n\n- `githubapp.ClientMetrics` emits the standard metrics described below\n- `githubapp.ClientLogging` logs metadata about all requests and responses\n\n```go\nbaseHandler, err := githubapp.NewDefaultCachingClientCreator(\n    config.Github,\n    githubapp.WithClientUserAgent(\"example-app/1.0.0\"),\n    githubapp.WithClientCaching(false, func() httpcache.Cache { return httpcache.NewMemoryCache() }),\n    githubapp.WithClientMiddleware(\n        githubapp.ClientMetrics(registry),\n        githubapp.ClientLogging(zerolog.DebugLevel),\n    ),\n    ...\n)\n```\n\n## Metrics\n\n`go-githubapp` uses [rcrowley/go-metrics][] to provide metrics. Metrics are\noptional and disabled by default.\n\nGitHub clients emit the following metrics when configured with the\n`githubapp.ClientMetrics` middleware:\n\n| metric name | type | definition |\n| ----------- | ---- | ---------- |\n| `github.requests` | `counter` | the count of successfully completed requests made to GitHub |\n| `github.requests.2xx` | `counter` | like `github.requests`, but only counting 2XX status codes |\n| `github.requests.3xx` | `counter` | like `github.requests`, but only counting 3XX status codes |\n| `github.requests.4xx` | `counter` | like `github.requests`, but only counting 4XX status codes |\n| `github.requests.5xx` | `counter` | like `github.requests`, but only counting 5XX status codes |\n| `github.requests.cached` | `counter` | the count of successfully cached requests |\n| `github.rate.limit[installation:\u003cid\u003e]` | `gauge` | the maximum number of requests permitted to make per hour, tagged with the installation id |\n| `github.rate.remaining[installation:\u003cid\u003e]` | `gauge` | the number of requests remaining in the current rate limit window, tagged with the installation id |\n\nWhen using [asynchronous dispatch](#asynchronous-dispatch), the\n`githubapp.WithSchedulingMetrics` option emits the following metrics:\n\n| metric name | type | definition |\n| ----------- | ---- | ---------- |\n| `github.event.queue` | `gauge` | the number of queued unprocessed event |\n| `github.event.workers` | `gauge` | the number of workers actively processing events |\n| `github.event.dropped` | `counter` | the number events dropped due to limited queue capacity |\n| `github.event.age` | `histogram` | the age (queue time) in milliseconds of events at processing time |\n\nThe `MetricsErrorCallback` and `MetricsAsyncErrorCallback` error callbacks for\nthe event dispatcher and asynchronous schedulers emit the following metrics:\n\n| metric name | type | definition |\n| ----------- | ---- | ---------- |\n| `github.handler.error[event:\u003ctype\u003e]` | `counter` | the number of processing errors, tagged with the GitHub event type |\n\nNote that metrics need to be published in order to be useful. Several\n[publishing options][] are available or you can implement your own.\n\n[rcrowley/go-metrics]: https://github.com/rcrowley/go-metrics\n[publishing options]: https://github.com/rcrowley/go-metrics#publishing-metrics\n\n## Background Jobs and Multi-Organization Operations\n\nWhile applications will mostly operate on the installation IDs provided in\nwebhook payloads, sometimes there is a need to run background jobs or make API\ncalls against multiple organizations. In these cases, use an _application\nclient_ to look up specific installations of the application and then construct\nan _installation client_ to make API calls:\n\n```go\nfunc getOrganizationClient(cc githubapp.ClientCreator, org string) (*github.Client, error) {\n    // create a client to perform actions as the application\n    appClient, err := cc.NewAppClient()\n    if err != nil {\n        return nil, err\n    }\n\n    // look up the installation ID for a particular organization\n    installations := githubapp.NewInstallationsService(appClient)\n\n    install, err := installations.GetByOwner(context.Background(), org)\n    if err != nil {\n        return nil, err\n    }\n\n    // create a client to perform actions on that specific organization\n    return cc.NewInstallationClient(install.ID)\n}\n\n```\n\n## Config Loading\n\nThe `appconfig` package provides a flexible configuration loader for finding\nrepository configuration. It supports repository-local files, files containing\nremote references, and organization-level defaults.\n\nBy default, the loader will:\n\n1. Try a list of paths in the repository\n2. If a file exists at a path, load its contents\n3. If the contents define a remote reference, load the remote file. Otherwise,\n   return the contents.\n4. If no files exist in the repository, try a list of paths in a `.github`\n   repository owned by the same owner.\n\nUsers can customize the paths, the remote reference encoding, whether remote\nreferences are enabled, the name of the owner-level default repository, and\nwhether the owner-level default is enabled.\n\nThe standard remote reference encoding is YAML:\n\n```yaml\nremote: owner/repo\npath: config/app.yml\nref: develop\n```\n\nUsage is straightforward:\n\n```go\nfunc loadConfig(ctx context.Context, client *github.Client, owner, repo, ref string) (*AppConfig, error) {\n    loader := appconfig.NewLoader([]string{\".github/app.yml\"})\n\n    c, err := loader.LoadConfig(ctx, client, onwer, repo, ref)\n    if err != nil {\n        return nil, err\n    }\n    if c.IsUndefined() {\n        return nil, nil\n    }\n\n    var appConfig AppConfig\n    if err := yaml.Unmarshal(c.Content, \u0026appConfig); err != nil {\n        return nil, err\n    }\n    return \u0026appConfig, nil\n}\n```\n\n## OAuth2\n\nThe `oauth2` package provides an `http.Handler` implementation that simplifies\nOAuth2 authentication with GitHub. When a user visits the endpoint, they are\nredirected to GitHub to authorize the application. GitHub redirects back to the\nsame endpoint, which performs the code exchange and obtains a token for the\nuser. The token is passed to a callback for further processing.\n\n```go\nfunc registerOAuth2Handler(c githubapp.Config) {\n    http.Handle(\"/api/auth/github\", oauth2.NewHandler(\n        oauth2.GetConfig(c, []string{\"user:email\"}),\n        // force generated URLs to use HTTPS; useful if the app is behind a reverse proxy\n        oauth2.ForceTLS(true),\n        // set the callback for successful logins\n        oauth2.OnLogin(func(w http.ResponseWriter, r *http.Request, login *oauth2.Login) {\n            // look up the current user with the authenticated client\n            client := github.NewClient(login.Client)\n            user, _, err := client.Users.Get(r.Context(), \"\")\n            // handle error, save the user, ...\n\n            // redirect the user back to another page\n            http.Redirect(w, r, \"/dashboard\", http.StatusFound)\n        }),\n    ))\n}\n```\n\nProduction applications should also use the `oauth2.WithStore` option to set a\nsecure `StateStore` implementation. `oauth2.SessionStateStore` is a good choice\nthat uses [alexedwards/scs](https://github.com/alexedwards/scs) to store the\nstate in a session.\n\n## Customizing Webhook Responses\n\nFor most applications, the default responses should be sufficient: they use\ncorrect status codes and include enough information to match up GitHub delivery\nrecords with request logs. If your application has additional requirements for\nresponses, two methods are provided for customization:\n\n- Error responses can be modified with a custom error callback. Use the\n  `WithErrorCallback` option when creating an event dispatcher.\n\n- Non-error responses can be modified with a custom response callback. Use the\n  `WithResponseCallback` option when creating an event dispatcher.\n\n- Individual hook responses can be modified by calling the `SetResponder`\n  function before the handler returns. Note that if you register a custom\n  response handler as described above, you must make it aware of handler-level\n  responders if you want to keep using `SetResponder`. See the default response\n  callback for an example of how to implement this.\n\n## Stability and Versioning Guarantees\n\nWhile we've used this library to build multiple applications internally,\nthere's still room for API tweaks and improvements as we find better ways to\nsolve problems. These will be backwards compatible when possible and should\nrequire only minor changes when not.\n\nReleases will be tagged periodically and will follow semantic versioning, with\nnew major versions tagged after any backwards-incompatible changes. Still, we\nrecommend vendoring this library to avoid surprises.\n\nIn general, fixes will only be applied to trunk and future releases, not\nbackported to older versions.\n\n## Contributing\n\nContributions and issues are welcome. For new features or large contributions,\nwe prefer discussing the proposed change on a GitHub issue prior to a PR.\n\nNew functionality should avoid adding new dependencies if possible and should\nbe broadly useful. Feature requests that are specific to certain uses will\nlikely be declined unless they can be redesigned to be generic or optional.\n\nBefore submitting a pull request, please run tests and style checks:\n\n```\n./godelw verify\n```\n\n## License\n\nThis library is made available under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalantir%2Fgo-githubapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpalantir%2Fgo-githubapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalantir%2Fgo-githubapp/lists"}