Giter Site home page Giter Site logo

palantir / go-githubapp Goto Github PK

View Code? Open in Web Editor NEW
321.0 204.0 57.0 469 KB

A simple Go framework for building GitHub Apps

Home Page: https://pkg.go.dev/github.com/palantir/go-githubapp

License: Apache License 2.0

Go 92.28% Shell 7.72%
golang github-app framework octo-correct-managed

go-githubapp's Introduction

go-githubapp GoDoc

A library for building GitHub Apps and other services that handle GitHub webhooks.

The library provides an http.Handler implementation that dispatches webhook events to the correct place, removing boilerplate and letting you focus on the logic of your application.

Usage

Most users will implement githubapp.EventHandler for each webhook event that needs to be handled. A single implementation can also respond to multiple event types if they require the same actions:

type CommentHandler struct {
    githubapp.ClientCreator
}

func (h *CommentHandler) Handles() []string {
    return []string{"issue_comment"}
}

func (h *CommentHandler) Handle(ctx context.Context, eventType, deliveryID string, payload []byte) error {
    // from github.com/google/go-github/github
    var event github.IssueCommentEvent
    if err := json.Unmarshal(payload, &event); err != nil {
        return err
    }

    // do something with the content of the event
}

We recommend embedding githubapp.ClientCreator in handler implementations as an easy way to access GitHub clients.

Once you define handlers, register them with an event dispatcher and associate it with a route in any net/http-compatible HTTP router:

func registerRoutes(c githubapp.Config) {
    cc := githubapp.NewDefaultCachingClientCreator(c)

    http.Handle("/api/github/hook", githubapp.NewDefaultEventDispatcher(c,
        &CommentHandler{cc},
        // ...
    ))
}

We recommend using go-baseapp as the minimal server framework for writing github apps, though go-githubapp works well with the standard library and can be easily integrated into most existing frameworks.

Examples

The example package contains a fully functional server using go-githubapp. The example app responds to comments on pull requests by commenting with a copy of the comment body.

To run the app, update example/config.yml with appropriate secrets and then run:

./godelw run example

Dependencies

go-githubapp has minimal dependencies, but does make some decisions:

Logging and metrics are only active when they are configured (see below). This means you can add your own logging or metrics libraries without conflict, but will miss out on the free built-in support.

Asynchronous Dispatch

GitHub imposes timeouts on webhook delivery responses. If an application does not respond in time, GitHub closes the connection and marks the delivery as failed. go-githubapp optionally supports asynchronous dispatch to help solve this problem. When enabled, the event dispatcher sends a response to GitHub after validating the payload and then runs the event handler in a separate goroutine.

To enable, select an appropriate scheduler and configure the event dispatcher to use it:

dispatcher := githubapp.NewEventDispatcher(handlers, secret, githubapp.WithScheduler(
    githubapp.AsyncScheduler(),
))

The following schedulers are included in the library:

  • DefaultScheduler - a synchronous scheduler that runs event handlers in the current goroutine. This is the default mode.

  • AsyncScheduler - an asynchronous scheduler that handles each event in a new goroutine. This is the simplest asynchronous option.

  • QueueAsyncScheduler - an asynchronous scheduler that queues events and handles them with a fixed pool of worker goroutines. This is useful to limit the amount of concurrent work.

AsyncScheduler and QueueAsyncScheduler support several additional options and customizations; see the documentation for details.

Structured Logging

go-githubapp uses rs/zerolog for structured logging. A logger must be stored in the context.Context associated with each http.Request:

func (d *EventDispatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    logger := zerolog.Ctx(r.Context())
    ...
}

If there is no logger in the context, log output is disabled. It's expected that HTTP middleware, like that provided by the hlog package, configures the http.Request context automatically.

Below are the standard keys used when logging events. They are also exported as constants.

exported constant key definition
LogKeyEventType github_event_type the github event type header
LogKeyDeliveryID github_delivery_id the github event delivery id header
LogKeyInstallationID github_installation_id the installation id the app is authenticating with
LogKeyRepositoryName github_repository_name the repository name of the pull request being acted on
LogKeyRepositoryOwner github_repository_owner the repository owner of the pull request being acted on
LogKeyPRNum github_pr_num the number of the pull request being acted on

Where appropriate, the library creates derived loggers with the above keys set to the correct values.

GitHub Clients

Authenticated and configured GitHub clients can be retrieved from githubapp.ClientCreator implementations. The library provides a basic implementation and a caching version.

There are three types of clients and two API versions for a total of six distinct clients:

  • An application client authenticates as the application and can only call limited APIs that mostly return application metadata.
  • An installation client authenticates as an installation and can call any APIs where the has been installed and granted permissions.
  • A token client authenticates with a static OAuth2 token associated with a user account.

go-githubapp also exposes various configuration options for GitHub clients. These are provided when calling githubapp.NewClientCreator:

  • githubapp.WithClientUserAgent sets a User-Agent string for all clients
  • githubapp.WithClientTimeout sets a timeout for requests made by all clients
  • githubapp.WithClientCaching enables response caching for all v3 (REST) clients. The cache can be configured to always validate responses or to respect the cache headers returned by GitHub. Re-validation is useful if data often changes faster than the requested cache duration.
  • githubapp.WithClientMiddleware allows customization of the http.RoundTripper used by all clients and is useful if you want to log requests or emit metrics about GitHub requests and responses.

The library provides the following middleware:

  • githubapp.ClientMetrics emits the standard metrics described below
  • githubapp.ClientLogging logs metadata about all requests and responses
baseHandler, err := githubapp.NewDefaultCachingClientCreator(
    config.Github,
    githubapp.WithClientUserAgent("example-app/1.0.0"),
    githubapp.WithClientCaching(false, func() httpcache.Cache { return httpcache.NewMemoryCache() }),
    githubapp.WithClientMiddleware(
        githubapp.ClientMetrics(registry),
        githubapp.ClientLogging(zerolog.DebugLevel),
    ),
    ...
)

Metrics

go-githubapp uses rcrowley/go-metrics to provide metrics. Metrics are optional and disabled by default.

GitHub clients emit the following metrics when configured with the githubapp.ClientMetrics middleware:

metric name type definition
github.requests counter the count of successfully completed requests made to GitHub
github.requests.2xx counter like github.requests, but only counting 2XX status codes
github.requests.3xx counter like github.requests, but only counting 3XX status codes
github.requests.4xx counter like github.requests, but only counting 4XX status codes
github.requests.5xx counter like github.requests, but only counting 5XX status codes
github.requests.cached counter the count of successfully cached requests
github.rate.limit[installation:<id>] gauge the maximum number of requests permitted to make per hour, tagged with the installation id
github.rate.remaining[installation:<id>] gauge the number of requests remaining in the current rate limit window, tagged with the installation id

When using asynchronous dispatch, the githubapp.WithSchedulingMetrics option emits the following metrics:

metric name type definition
github.event.queue gauge the number of queued unprocessed event
github.event.workers gauge the number of workers actively processing events
github.event.dropped counter the number events dropped due to limited queue capacity
github.event.age histogram the age (queue time) in milliseconds of events at processing time

The MetricsErrorCallback and MetricsAsyncErrorCallback error callbacks for the event dispatcher and asynchronous schedulers emit the following metrics:

metric name type definition
github.handler.error[event:<type>] counter the number of processing errors, tagged with the GitHub event type

Note that metrics need to be published in order to be useful. Several publishing options are available or you can implement your own.

Background Jobs and Multi-Organization Operations

While applications will mostly operate on the installation IDs provided in webhook payloads, sometimes there is a need to run background jobs or make API calls against multiple organizations. In these cases, use an application client to look up specific installations of the application and then construct an installation client to make API calls:

func getOrganizationClient(cc githubapp.ClientCreator, org string) (*github.Client, error) {
    // create a client to perform actions as the application
    appClient, err := cc.NewAppClient()
    if err != nil {
        return nil, err
    }

    // look up the installation ID for a particular organization
    installations := githubapp.NewInstallationsService(appClient)
    install := installations.GetByOwner(context.Background(), org)

    // create a client to perform actions on that specific organization
    return cc.NewInstallationClient(install.ID)
}

Config Loading

The appconfig package provides a flexible configuration loader for finding repository configuration. It supports repository-local files, files containing remote references, and organization-level defaults.

By default, the loader will:

  1. Try a list of paths in the repository
  2. If a file exists at a path, load its contents
  3. If the contents define a remote reference, load the remote file. Otherwise, return the contents.
  4. If no files exist in the repository, try a list of paths in a .github repository owned by the same owner.

Users can customize the paths, the remote reference encoding, whether remote references are enabled, the name of the owner-level default repository, and whether the owner-level default is enabled.

The standard remote reference encoding is YAML:

remote: owner/repo
path: config/app.yml
ref: develop

Usage is straightforward:

func loadConfig(ctx context.Context, client *github.Client, owner, repo, ref string) (*AppConfig, error) {
    loader := appconfig.NewLoader([]string{".github/app.yml"})

    c, err := loader.LoadConfig(ctx, client, onwer, repo, ref)
    if err != nil {
        return nil, err
    }
    if c.IsUndefined() {
        return nil, nil
    }

    var appConfig AppConfig
    if err := yaml.Unmarshal(c.Content, &appConfig); err != nil {
        return nil, err
    }
    return &appConfig, nil
}

OAuth2

The oauth2 package provides an http.Handler implementation that simplifies OAuth2 authentication with GitHub. When a user visits the endpoint, they are redirected to GitHub to authorize the application. GitHub redirects back to the same endpoint, which performs the code exchange and obtains a token for the user. The token is passed to a callback for further processing.

func registerOAuth2Handler(c githubapp.Config) {
    http.Handle("/api/auth/github", oauth2.NewHandler(
        oauth2.GetConfig(c, []string{"user:email"}),
        // force generated URLs to use HTTPS; useful if the app is behind a reverse proxy
        oauth2.ForceTLS(true),
        // set the callback for successful logins
        oauth2.OnLogin(func(w http.ResponseWriter, r *http.Request, login *oauth2.Login) {
            // look up the current user with the authenticated client
            client := github.NewClient(login.Client)
            user, _, err := client.Users.Get(r.Context(), "")
            // handle error, save the user, ...

            // redirect the user back to another page
            http.Redirect(w, r, "/dashboard", http.StatusFound)
        }),
    ))
}

Production applications should also use the oauth2.WithStore option to set a secure StateStore implementation. oauth2.SessionStateStore is a good choice that uses alexedwards/scs to store the state in a session.

Customizing Webhook Responses

For most applications, the default responses should be sufficient: they use correct status codes and include enough information to match up GitHub delivery records with request logs. If your application has additional requirements for responses, two methods are provided for customization:

  • Error responses can be modified with a custom error callback. Use the WithErrorCallback option when creating an event dispatcher.

  • Non-error responses can be modified with a custom response callback. Use the WithResponseCallback option when creating an event dispatcher.

  • Individual hook responses can be modified by calling the SetResponder function before the handler returns. Note that if you register a custom response handler as described above, you must make it aware of handler-level responders if you want to keep using SetResponder. See the default response callback for an example of how to implement this.

Stability and Versioning Guarantees

While we've used this library to build multiple applications internally, there's still room for API tweaks and improvements as we find better ways to solve problems. These will be backwards compatible when possible and should require only minor changes when not.

Releases will be tagged periodically and will follow semantic versioning, with new major versions tagged after any backwards-incompatible changes. Still, we recommend vendoring this library to avoid surprises.

In general, fixes will only be applied to trunk and future releases, not backported to older versions.

Contributing

Contributions and issues are welcome. For new features or large contributions, we prefer discussing the proposed change on a GitHub issue prior to a PR.

New functionality should avoid adding new dependencies if possible and should be broadly useful. Feature requests that are specific to certain uses will likely be declined unless they can be redesigned to be generic or optional.

Before submitting a pull request, please run tests and style checks:

./godelw verify

License

This library is made available under the Apache 2.0 License.

go-githubapp's People

Contributors

0xiso avatar ajlake avatar akerl avatar andygrunwald avatar asvoboda avatar bluekeyes avatar data-douser avatar dependabot[bot] avatar domsu avatar fawind avatar iainsteers avatar jamestoyer avatar jetersen avatar jmcampanini avatar kazuki-hanai avatar mortenmj avatar nvelat avatar quike avatar steebchen avatar svc-excavator-bot avatar wwong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-githubapp's Issues

Pass token to net/http for asset download

Looking for some advice on a possible approach of how to obtain the app's authorization token for net/http (or any method which allows me to do what I need to do)

I initiate an authorised session

type MyHandler struct {
	ClientCreator githubapp.ClientCreator
}
client, err := h.ClientCreator.NewInstallationClient(installationID)

This then allows me to get release assets from a private repository using github-go's DownloadReleaseAssets

client.Repositories.DownloadReleaseAsset(ctx, "owner", "repo", assetID, http.DefaultClient)

This in turn corresponds to the following GitHub API which returns a json body, from which you can get a Download URL ( browser_download_url )

To do this, I need to use a standard http GET (not found any other method yet in github-go), however for that, I need a token for the Authorization Bearer header.

Is there anyway for me to somehow wrap client here, or pull the token out that can be used in the GET request?

Example app clarification

Hello,

I have set up and configured the example app. When I make a pull request on a repo which has granted access to my GitHub APP, I can see it makes a successful call to the webhook

{"level":"info","github_event_type":"push","github_delivery_id":"7111f570-85c5-11ed-84eb-8240ffa8947c","time":"2022-12-27T09:04:18Z","message":"Received webhook event"}

image

image

However nothing occurs from there, no comments are made to any PR or issues.

I will go and have a read over the code and figure it out, but thought it worth raising this to give the developers some end user / UX for someone green.

PrepareRepoContext should take an interface

func PrepareRepoContext(ctx context.Context, installationID int64, repo *github.Repository) (context.Context, zerolog.Logger) {

go-github has a variety of repository-like objects, notably Repository and PushEventRepository. Since PrepareRepoContext only cares about a few fields accessed via getters, we should define an interface to accommodate the various repository objects.

We should use the same interface in PreparePRContext.

Add rate limiting retry middleware

It would be helpful if the client creator provided an option to enable delays and retries in case of rate limiting. This should be optional because GitHub's hourly buckets mean you might have to wait a long time to try again. This also implies there should be an option for a maximum wait time, so if the retry header is past this value, the request can fail immediately. I expect this will be most useful if accidentally hitting the abuse limits (where the wait time is probably minutes), but could also be useful for regular limits.

Emit metrics for async webhook processing errors

When using synchronous processing, errors rates in webhook handlers can be collected by server-level metrics for response codes. With async processing, all responses are successful, so this doesn't work. We should add a new counter metric that is incremented every time webhook processing ends in an error, regardless of the dispatch mode.

Related to the dropped event count metric requested in #42.

Upgrade to v2 of github.com/alexedwards/scs

There is a new major version of github.com/alexedwards/scs which looks like it has some breaking changes. We should either update to it or consider moving to a new package as this one appears to have not been touched in over a year

Add GraphQL query logging middleware

While the existing request logging is great for understanding how the REST API is used, applications using GraphQL see only that a GraphQL request was made. It would be nice to have middleware for the v4 clients that logs queries. Ideally we only log queries and not mutations, but I don't know if it is possible to tell the difference without parsing (or if you can combine them in a single request.)

InstallationsService#GetByOwner only works for organizations

If an installation is owned by a user, code that calls the default implementation of GetByOwner will fail, because it only checks for organization installations.

Since organization installations are the most common use for us internally, try that first, but if we get a 404 response, try the user endpoint as well.

Context Deadline Exceeded in Google Cloud Run

I'm building a Github Application which listens to PR events and creates Check Runs. I deployed the app to Google Cloud Run, and when attempting to send anything outbound to GitHub API, the following is returned:

Post "https://api.github.com/repos/ex0dus-0x/pkg-bkdoor-poc/check-runs": could not refresh installation id 19058859's token: could not get access_tokens from GitHub API for installation ID 19058859: context deadline exceeded (Client.Timeout exceeded while awaiting headers)

The corresponding code in the handler:

	// instantiate authenticated client and context
	installationID := githubapp.GetInstallationIDFromEvent(&event)
	ctx, logger := githubapp.PreparePRContext(ctx, installationID, repo, prNum)
	client, err := h.NewInstallationClient(installationID)
	if err != nil {
		return errors.Wrap(err, "cannot instantiate client for interaction")
	}

	logger.Debug().Msgf("Creating new Check Run")
	name := "Scan"
	run, _, err := client.Checks.CreateCheckRun(ctx, author, repoName,
		github.CreateCheckRunOptions{
			Name:    name,
			HeadSHA: pr.GetHead().GetSHA(),
		},
	)
        if err != nil {
            return err
        }

I've tried to manually update the deadline to something longer, but that didn't seem to work. Any ideas why this is happening and how to fix? Also sent an issue to go-github.

Type mismatch with third-party library method parameter

Description

It looks like there's a conflict between this library and the bradleyfalzon/ghinstallation library:

cannot use integrationID (type int) as type int64 in argument to ghinstallation.NewAppsTransport

This pops up when I attempt to build my project which uses the go-githubapp library. I assume the fix here is to change the type of the value passed into those ghinstallation.NewAppsTransport and ghinstallation.New methods to be int64 to comply with that library's requirements? If so, I can open a PR to address it - just let me know!

howto set CA to trust, or set tls options

Is there any way to configure the underlying http-client with the CA to trust, or to set InsecureSkipVerify when communicating with the GH-api? Folks running gh server might need that.

can't use app authentication with v4 client

I setup my client like this: https://github.com/davidkarlsen/group-sync-operator/blob/issue111/pkg/syncer/github.go#L168
using a certificate + appId, this works perfectly for the v3 client, but the v4 client will fail with: [1].
If I use the v4 client based on a token it works just fine. This seems like a bug.

[1]

goroutine 176 [running]:
k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0xc0009b74e0})
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/runtime/runtime.go:55 +0xd8
panic({0x2de4de0, 0x4c529a0})
        /usr/local/opt/go/libexec/src/runtime/panic.go:1038 +0x215
github.com/bradleyfalzon/ghinstallation/v2.(*AppsTransport).RoundTrip(0xc0006df580, 0xc0000e1100)
        /Users/et2448/go/pkg/mod/github.com/bradleyfalzon/ghinstallation/[email protected]/appsTransport.go:87 +0x5ec
github.com/bradleyfalzon/ghinstallation/v2.(*Transport).refreshToken(0xc000351aa0, {0x35435c8, 0xc00011a1d8})
        /Users/et2448/go/pkg/mod/github.com/bradleyfalzon/ghinstallation/[email protected]/transport.go:179 +0x61c
github.com/bradleyfalzon/ghinstallation/v2.(*Transport).Token(0xc000351aa0, {0x35435c8, 0xc00011a1d8})
        /Users/et2448/go/pkg/mod/github.com/bradleyfalzon/ghinstallation/[email protected]/transport.go:131 +0x15a
github.com/bradleyfalzon/ghinstallation/v2.(*Transport).RoundTrip(0xc000351aa0, 0xc0000e0f00)
        /Users/et2448/go/pkg/mod/github.com/bradleyfalzon/ghinstallation/[email protected]/transport.go:113 +0x67
github.com/palantir/go-githubapp/githubapp.setUserAgentHeader.func1.1(0xc0000e0f00)
        /Users/et2448/go/pkg/mod/github.com/palantir/[email protected]/githubapp/client_creator.go:428 +0x10a
github.com/palantir/go-githubapp/githubapp.roundTripperFunc.RoundTrip(0x30, 0x3514000)
        /Users/et2448/go/pkg/mod/github.com/palantir/[email protected]/githubapp/middleware.go:150 +0x1f
net/http.send(0xc0000e0f00, {0x3514000, 0xc0006d2480}, {0x320fae0, 0x203001, 0x0})
        /usr/local/opt/go/libexec/src/net/http/client.go:252 +0x5d8
net/http.(*Client).send(0xc0006d2390, 0xc0000e0f00, {0x0, 0x8, 0x0})
        /usr/local/opt/go/libexec/src/net/http/client.go:176 +0x9b
net/http.(*Client).do(0xc0006d2390, 0xc0000e0f00)
        /usr/local/opt/go/libexec/src/net/http/client.go:725 +0x908
net/http.(*Client).Do(...)
        /usr/local/opt/go/libexec/src/net/http/client.go:593
golang.org/x/net/context/ctxhttp.Do({0x35435c8, 0xc00011a1d8}, 0x329579b, 0xc0000e0d00)
        /Users/et2448/go/pkg/mod/golang.org/x/[email protected]/context/ctxhttp/ctxhttp.go:27 +0x206
golang.org/x/net/context/ctxhttp.Post({0x35435c8, 0xc00011a1d8}, 0xc000532348, {0xc00098ca80, 0x1e}, {0x329bdcc, 0x10}, {0x3510300, 0xc0006d2c30})
        /Users/et2448/go/pkg/mod/golang.org/x/[email protected]/context/ctxhttp/ctxhttp.go:65 +0x175
github.com/shurcooL/graphql.(*Client).do(0xc00000ff38, {0x35435c8, 0xc00011a1d8}, 0x5, {0x2bff940, 0xc0006d2b40}, 0xc0006d2b70)
        /Users/et2448/go/pkg/mod/github.com/shurcoo!l/[email protected]/graphql.go:68 +0x1b2
github.com/shurcooL/graphql.(*Client).Query(...)
        /Users/et2448/go/pkg/mod/github.com/shurcoo!l/[email protected]/graphql.go:37
github.com/shurcooL/githubv4.(*Client).Query(...)
        /Users/et2448/go/pkg/mod/github.com/shurcoo!l/[email protected]/githubv4.go:42
github.com/redhat-cop/group-sync-operator/pkg/syncer.(*GitHubSyncer).getScimIdentity(0xc000838630)
        /Users/et2448/projects/ext/github.com/group-sync-operator/pkg/syncer/github.go:334 +0x185
github.com/redhat-cop/group-sync-operator/pkg/syncer.(*GitHubSyncer).Sync(0xc000838630)
        /Users/et2448/projects/ext/github.com/group-sync-operator/pkg/syncer/github.go:251 +0x1fa
github.com/redhat-cop/group-sync-operator/controllers.(*GroupSyncReconciler).Reconcile(0xc00007ec30, {0x3543638, 0xc000300e10}, {{{0xc0005a0b28, 0x13}, {0xc000886a00, 0x10}}})
        /Users/et2448/projects/ext/github.com/group-sync-operator/controllers/groupsync_controller.go:111 +0xbc2
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler(0xc0002ae6e0, {0x3543590, 0xc0005346c0}, {0x2f924e0, 0xc0009b74e0})
        /Users/et2448/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:298 +0x303
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem(0xc0002ae6e0, {0x3543590, 0xc0005346c0})
        /Users/et2448/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:253 +0x205
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func1.2({0x3543590, 0xc0005346c0})
        /Users/et2448/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:216 +0x46
k8s.io/apimachinery/pkg/util/wait.JitterUntilWithContext.func1()
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:185 +0x25
k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1(0xbc012a0)
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:155 +0x67
k8s.io/apimachinery/pkg/util/wait.BackoffUntil(0xc00029a480, {0x3512f00, 0xc000300c30}, 0x1, 0xc000828720)
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:156 +0xb6
k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc0005b4750, 0x3b9aca00, 0x0, 0x0, 0x1000000020000)
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:133 +0x89
k8s.io/apimachinery/pkg/util/wait.JitterUntilWithContext({0x3543590, 0xc0005346c0}, 0xc00062b660, 0x0, 0xc0002d8278, 0x0)
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:185 +0x99
k8s.io/apimachinery/pkg/util/wait.UntilWithContext({0x3543590, 0xc0005346c0}, 0x1e20626, 0xc0002d81c0)
        /Users/et2448/go/pkg/mod/k8s.io/[email protected]/pkg/util/wait/wait.go:99 +0x2b
created by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func1
        /Users/et2448/go/pkg/mod/sigs.k8s.io/[email protected]/pkg/internal/controller/controller.go:213 +0x356

Add InstallationService#GetByRepository

https://developer.github.com/v3/apps/#get-a-repository-installation-for-the-authenticated-app

Currently, GetByOwner tries organizations first, then users if the organization endpoint returns a 404. In my local testing against our internal GitHub Enterprise instance, it can take 2-3s for the organization endpoint to return a 404, adding a noticeable delay to operations that need to look up an installation ID for user accounts.

In almost all cases, we actually care about a specific repository, so my hypothesis is that a positive match via the repository endpoint will be faster than the fallback strategy.

PR #212 has broken the example code

PR #212 has broken the example code.

installationID := githubapp.GetInstallationIDFromEvent(&event)

cannot use &event (value of type *"github.com/google/go-github/v48/github".IssueCommentEvent) as githubapp.InstallationSource value in argument to githubapp.GetInstallationIDFromEvent: *"github.com/google/go-github/v48/github".IssueCommentEvent does not implement githubapp.InstallationSource (wrong type for method GetInstallation)
		have GetInstallation() *"github.com/google/go-github/v48/github".Installation
		want GetInstallation() *"github.com/google/go-github/v47/github".Installation

ctx, logger := githubapp.PreparePRContext(ctx, installationID, repo, event.GetIssue().GetNumber())

cannot use repo (variable of type *"github.com/google/go-github/v48/github".Repository) as *"github.com/google/go-github/v47/github".Repository value in argument to githubapp.PreparePRContext

Track queue time/event age for async dispatch

It would be nice to have a github.event.age metric or similar, that reports statistics about the age (amount of time spent in the queue) of webhook events. Should only have to record the current time when adding to the queue and then compare it to the current time at the start or end of processing.

Include docs on creating github app for example

Would it be possible to add code above the “example” section of the docs, or in a readme in that directory, that walks through the right github app settings for running the example?

As a first-time user of this library, I went to try the example. Running the example server was super straightforward, but I was left to guess at what options to set when creating my github app. Specifically, I had to infer what permissions to ask for, given the description of what the app would do, and I had to search in the code to find what “DefaultWebhookRoute” referred to.

Errors loading config larger than 1MB

I think this has been broken since we introduced the appconfig package, but it's also possible that GitHub's API changed. The GetContents method returns content if the file is less than 1MB and an empty content field in the JSON for content between 1MB and 100MB. The endpoint returns an error for content over 100MB.

We handle the case where the content is larger than 100MB, but do not handle the case where the content is larger than 1MB. Instead, we return an error when we try to decode content with the none encoding.

When the content field is empty, I think there are two options:

  1. Make another request to the "Get repository contents" API, but provide the raw media type
  2. Switch to using the DownloadContents method, like we do when the file is larger than 100MB

The reason to not use DownloadContents every time is that it makes two API calls: one to list the directory containing the file, and then one to download the file. Since most apps perform configuration lookups all the time, minimizing requests here is a good idea.

Consider defining new module in `example` package

Given the current way Go modules work, defining the example packages causes consumers of this library to record dependencies of the example package in their go.sum files, where they can be discovered by security scanners and dependency analyzers. These dependencies are not used by the importable packages of the library and many of them may not even be used by the example package.

I'll need to do more research, but I believe by defining a go.mod file in the example directory we can exclude it (and its dependencies) from the dependency graph of the primary module without causing any issues for maintenance or consumers (since you can't import the example package.)

Improve base URL configuration

// originally suggested by @bluekeyes

Currently, users have to specify the base URL three times in configuration:

  1. The web URL, used for the OAuth login flow
  2. The v3 API URL
  3. The v4 API URL

It seems like we should be able to compute all of these given a single base URL, as long as we assume that github.com works in a known consistent way (most other client libraries seem to assume this.) Something like:

  • If if the base URL host is github.com, use api.github.com and api.github.com/graphql
  • Otherwise, assume an enterprise installation and use baseURL/api/v3 and baseURL/api/v4/graphql

The place where this would break is if someone is running a named proxy for github.com (i.e. not using the native proxy support in http.Client) or if someone put a proxy in front of their enterprise install to mess with the URLs. I don't think it's worth support either of those cases.

Maybe something like this:

type URLs struct {
  Web   *url.URL
  APIv3 *url.URL
  APIv4 *url.URL
}

func NewURLs(baseURL string) (*URLs, error) {
  // parse baseURL, set others based on if it contains github.com or not
}

Then ClientCreator could take one of these objects and BaseHandler could expose one for use in other places if needed.

Configure redirect_uri for token request

It's possible to specify a redirect_uri in the initial OAuth login, but a default redirect_uri is set after the successful login when fetching a token. However, it seems like if you have a specific setup (like a reverse proxy in between your frontend and your API), it uses the host of the reverse proxy URL which results in the redirect_uri to not match.

The part I'm talking about:

https://github.com/palantir/go-githubapp/blob/develop/oauth2/handler.go#L131

Why is this not configurable? I'd wish it was, like this:

oauth2.NewHandler(
	oauth.SetRedirectURL(url),
	// ...
)

I tried doing this:

cfg := oauth2.GetConfig(config.Config.Github, []string{"user:email"})
cfg.RedirectURL = getRedirectURL()
return oauth2.NewHandler(
	cfg,
	// ...
)

However, this doesn't work as the redirect URL is overwritten in oauth2.NewHandler.

404 page not found

when creating a github app on github.com for the example and running:

go run ./example

web hook delivery fails with 404 page not found

Exposing metrics via HTTP

I see that https://github.com/rcrowley/go-metrics is used and implemented.
Also, the metrics middleware to collect metrics is used.
Just wanna check what is the preferred way to expose metrics via HTTP for go-githubapp or go-baseapp?

Is it the usage of github.com/rcrowley/go-metrics/exp itself like

import "github.com/rcrowley/go-metrics/exp"

[...]

app.httpServer.Mux().Handle(pat.Get("/debug/metrics"), exp.ExpHandler(app.httpServer.Registry()))

or is there a better alternative?

app.httpServer in this case is *baseapp.Server.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.