{"id":16345384,"url":"https://github.com/knl/pulley","last_synced_at":"2026-03-08T02:01:36.917Z","repository":{"id":64307477,"uuid":"236698346","full_name":"knl/pulley","owner":"knl","description":"A service to expose Prometheus metrics of your CI’s validation of Pull Requests, using GitHub webhooks.","archived":false,"fork":false,"pushed_at":"2023-02-15T06:13:41.000Z","size":70,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-10T19:48:14.420Z","etag":null,"topics":["github-webhooks","golang","prometheus","service"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/knl.png","metadata":{"files":{"readme":"README.adoc","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}},"created_at":"2020-01-28T09:23:20.000Z","updated_at":"2024-08-27T15:16:47.000Z","dependencies_parsed_at":"2024-06-20T08:28:43.226Z","dependency_job_id":null,"html_url":"https://github.com/knl/pulley","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/knl/pulley","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knl%2Fpulley","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knl%2Fpulley/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knl%2Fpulley/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knl%2Fpulley/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/knl","download_url":"https://codeload.github.com/knl/pulley/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knl%2Fpulley/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30242403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T00:58:18.660Z","status":"online","status_checked_at":"2026-03-08T02:00:06.215Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["github-webhooks","golang","prometheus","service"],"created_at":"2024-10-11T00:31:19.543Z","updated_at":"2026-03-08T02:01:36.874Z","avatar_url":"https://github.com/knl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Pulley\n\nA service to expose Prometheus metrics of your CI's validation of Pull Requests\n(PRs), using GitHub webhooks.\n\nimage:https://github.com/knl/pulley/workflows/CI/badge.svg[CI]\nimage:https://github.com/knl/pulley/workflows/goreleaser/badge.svg[release]\nimage:https://goreportcard.com/badge/github.com/knl/pulley[link=https://goreportcard.com/report/github.com/knl/pulley, alt=Go Report Card]\nimage:https://codebeat.co/badges/56655dd4-44a7-45fd-9d33-0654b5a90452[link=https://codebeat.co/projects/github-com-knl-pulley-master]\n\n[ditaa]\n....\n                webhook\n+------------+  events    +-----------+\n| github.com +-----------\u003e+/          |\n+------------+            |           |  proxy  +--------+\n                          |           +\u003c-------\u003e+ pulley |\n                          |   nginx   |         +--------+\n+------------+            |           |\n| prometheus +------------+/metrics   |\n+------------+            +-----------+\n....\n\n\n== Why\n\nThe best way to have service level objectives (SLOs) in place is to measure the\nsame way the other party is observing. Sometimes your CI is providing you\nmetrics, but they are not sufficient or do not match the reality. Pulley uses\nGitHub's webhooks to produce metrics as observed by your developers. That way,\nyou can optimize your CI's pipelines in a way it matters.\n\n=== Features\n\nPulley assumes that a PR is mergeable when a required status check is\nsuccessful. Thus, it lets you configure which status checks to monitor, and only\ntreat the PR as validated (mergeable) when that status check is green. Just\nexactly like how developers (or even other automation) observe and act on PRs.\n\nThe basic metrics Pulley exposes are (all times are histograms):\n\n- The time it takes for the CI to send the first `pending` status for a PR\n- The time it takes for the CI to send the `success`/`failure`/`error` for the\n  required status check, from the time PR has been open\n- The time it takes for a PR to be merged since it got open\n- The build duration on the CI, per build\n- How many PRs have been open/closed\n- How many times branches have been rebased\n- The total number of status checks received\n- The total number of `success`/`failure`/`error` status checks received,\n  without a preceding `pending` status check\n\nPulley can track any repository on GitHub, as long as that repository is\nconfigured to send https://developer.github.com/webhooks/[webhook events] to it.\n\n== Usage\n\nPulley is a service that should run listening on a public IP (as an endpoint is\nneeded to be accessible by GitHub's servers). It is completely configurable via\nenvironment variables.\n\n=== Configuration\n\nPulley understands the following environment variables:\n\n|===\n| Environment Variable | Description\n\n| PULLEY_HOST\n| The hostname to which Pulley will bind. Defaults to `localhost`.\n\n| PULLEY_PORT\n| The port number to which Pulley will bind. Defaults to `1701`.\n\n| PULLEY_WEBHOOK_PATH\n| URL path on which Pulley receives webhooks. Defaults to an empty string,\n  meaning that webhook events are handled on the root path (that is,\n  `http://$PULLEY_HOST:$PULLEY_PORT/`).\n\n| PULLEY_WEBHOOK_TOKEN\n| A **base64** encoded string representing a secret token, used to validate the\n  events coming from GitHub. Defaults to an empty string. More details on\n  https://developer.github.com/webhooks/securing/.\n\n| PULLEY_METRICS_PATH\n| URL path on which Pulley exposes Prometheus metrics. Defaults to `metrics`.\n\n| PULLEY_PR_TIMING_STRATEGY\n| Which strategy Pulley should use to time the PRs. That is, how to detect when\n  PR building started and ended. Currently, the only available one is `regex`.\n  If missing or unset, defaults to `regex`.\n\n| PULLEY_STRATEGY_AGGREGATE_REPO_REGEX_\u003cint\u003e\n| Set of regular expressions defining contexts to monitor for matching\n  repository names. Defaults to regex `:all-jobs$` matching all repositories\n  (`.*`). For more details, consult the next section.\n\n| PULLEY_STRATEGY_AGGREGATE_CONTEXT_REGEX_\u003cint\u003e\n| See above.\n\n| PULLEY_TRACK_BUILD_TIMES\n| If true, Pulley will track and export build times for each build (that is,\n  different context it sees). Accepted range of values is `1`, `t`, `T`, `TRUE`,\n  `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False`.\n|===\n\n==== PR Timing Strategies\n\nA PR timing strategy is how Pulley determines when the CI started building the\nPR, and when CI finished. It deals with received status checks coming from\nGitHub.\n\nAt the moment, Pulley supports only a single PR timing strategy, `aggregate`.\n\n===== The aggregate strategy\n\nThis strategy assumes that each repository has one _aggregate_ job defined, that\nfinishes when all other jobs are done and emits a corresponding status check.\nThis job determines if the PR is to be merged. Another use of this strategy is\nfor the case when there is a single required status check defined in a repo.\n\nTo provide versatility in configuration, while keeping it simple and\nconfigurable via the environment variables, Pulley resorts to using the regular\nexpressions (regexes) and the following scheme for the environment variables\nwhen encoding that information:\n\n PULLEY_STRATEGY_AGGREGATE_REPO_REGEX_\u003cint\u003e = $repo_name_regex\n PULLEY_STRATEGY_AGGREGATE_CONTEXT_REGEX_\u003cint\u003e = $status_check_name_regex\n\nThat is, we mimic a prioritized list of regexes to match between the repository\nname and the status check name.\n\nThe list of regexes is processed in order from the smallest number towards the\nhighest, looking first for the `_REPO` variant. If there is a match on the\nrepository name, but not on the status, the search will **NOT** continue.\n\nFor example, the following configuration:\n\n PULLEY_STRATEGY_AGGREGATE_REPO_REGEX_0=-deployment$\n PULLEY_STRATEGY_AGGREGATE_CONTEXT_REGEX_0=^terraform-validate\n PULLEY_STRATEGY_AGGREGATE_REPO_REGEX_1=^knl/pulley$\n PULLEY_STRATEGY_AGGREGATE_CONTEXT_REGEX_1=build\n PULLEY_STRATEGY_AGGREGATE_REPO_REGEX_100=.*\n PULLEY_STRATEGY_AGGREGATE_CONTEXT_REGEX_100=:all-jobs$\n\nwould check for status checks whose names begin with `terraform-validate` for\nall repositories whose names end with `-deployment`. Then it would check for\n`build` status check on repositories whose names begin with `knl/pulley`.\nFinally, it would default (due to `.*` regex on the repository name) to status\ncheck whose name ends with `:all-jobs` for all other repositories.\n\nSeveral things are important to note here:\n\n. Repository names are always in the form: `$OWNER/$REPOSITORY`, that is, they\n  represent the repository's full name.\n. The first entry that matches the repository name is considered only.\n. When there are multiple matching status check names for a repository, only the\n  first one that shows up will be considered.\n\n== Run\n\nSet the environment variables and run:\n\n ./pulley\n\nThe best is to place Pulley behind a reverse proxy (for example, Nginx) that\nterminates HTTPS traffic.\n\n== Requirements\n\nGo version: `1.13`\n\n== Development\n\nTo build the code, simply run:\n\n make build\n\nSimilarly, the tests are executed via:\n\n make test\n\nPrior to committing the code, you could run\n\n make\n\nto properly format and lint the code\n\n=== Managing releases\n\nReleases are managed with https://goreleaser.com/[goreleaser].\n\nTo create a new release, push a tag (for example, a version 0.1.0):\n\n git tag -a v0.1.0 -m \"First release\"\n git push origin v0.1.0\n\nTo build a test release, without publishing, run:\n\n make test-release\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknl%2Fpulley","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fknl%2Fpulley","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknl%2Fpulley/lists"}