{"id":15101446,"url":"https://github.com/kwk/buildbot-app","last_synced_at":"2025-09-04T05:35:19.467Z","repository":{"id":157516984,"uuid":"624477434","full_name":"kwk/buildbot-app","owner":"kwk","description":"Your GitHub App to make Buildbot a part of your Pull Request workflow.","archived":false,"fork":false,"pushed_at":"2023-09-07T11:10:08.000Z","size":1376,"stargazers_count":2,"open_issues_count":8,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-06T13:45:32.962Z","etag":null,"topics":["buildbot","ci","git","llvm"],"latest_commit_sha":null,"homepage":"https://kwk.github.io/buildbot-app/","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/kwk.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null}},"created_at":"2023-04-06T14:54:37.000Z","updated_at":"2023-09-07T10:06:18.000Z","dependencies_parsed_at":"2024-02-18T07:32:29.683Z","dependency_job_id":"b22abe02-98c2-41cd-a3a2-158d57b42dce","html_url":"https://github.com/kwk/buildbot-app","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kwk/buildbot-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwk%2Fbuildbot-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwk%2Fbuildbot-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwk%2Fbuildbot-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwk%2Fbuildbot-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kwk","download_url":"https://codeload.github.com/kwk/buildbot-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwk%2Fbuildbot-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273556206,"owners_count":25126519,"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","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"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":["buildbot","ci","git","llvm"],"created_at":"2024-09-25T18:23:08.846Z","updated_at":"2025-09-04T05:35:19.406Z","avatar_url":"https://github.com/kwk.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"// DO NOT MODIFY THIS DOCUMENT DIRECTLY!\n// MODIFY docs/README.in.adoc INSTEAD!\n// THEN RUN make docs!\n= Your GitHub App to make Buildbot a part of your Pull Request workflow.\nKonrad Kleine \u003ckkleine@redhat.com\u003e;\n:description: A study to showcase how GitHub pull request comments and Check Runs can be used in combination with aminimally modified buildbot.\n:toc: left\n:toclevels: 5\n:showtitle:\n:experimental:\n:sectnums:\n:stem:\n:sectlinks:\n:listing-caption: Listing\n:sectanchors:\n// :icons: font\n:source-highlighter: pygments\n\n// See https://gist.github.com/dcode/0cfbf2699a1fe9b46ff04c41721dda74#admonitions\nifdef::env-github[]\n:tip-caption: :bulb:\n:note-caption: :information_source:\n:important-caption: :heavy_exclamation_mark:\n:caution-caption: :fire:\n:warning-caption: :warning:\nendif::[]\n\n// We always want our images to be displayed directly from github\n// See https://gist.github.com/dcode/0cfbf2699a1fe9b46ff04c41721dda74#images\n// ifdef::env-github[]\n:imagesdir: https://raw.githubusercontent.com/kwk/buildbot-app/main/\n// endif::[]\n\n// toc::[]\n[IMPORTANT]\n====\nTHIS REPOSITORY IS NO LONGER ACTIVELY MAINTAINED.\n====\n\nLet’s build a GitHub App that lets you control your buildbot through\n`/buildbot` comments on GitHub Pull Requests.\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/logo/logo-round-small.png[logo\nround small]\n\nimage:https://codecov.io/gh/kwk/buildbot-app/branch/main/graph/badge.svg?token=ZQ83LL4XLY[CodeCov]\n\nimage:https://dl.circleci.com/status-badge/img/gh/kwk/buildbot-app/tree/main.svg?style=svg[CircleCI]\n\n[[_scenarios]]\n== Scenarios\n\nThis section lists the ideas and sometimes even fully implemented\nscenarios we have for this GitHub app.\n\n[[_comment_buildbot_on_pull_request]]\n=== Comment `/buildbot` on Pull Request\n\nIn this scenario a user authors a Pull Request comment with the comment\nbody being `/buildbot`.\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/screenshots/author-buildbot-comment.png[author\nbuildbot comment]\n\nThe `buildbot-app` gets notified about a new comment and checks if it\nmatches a regular expression. This EBNF diagram shows the current\ncommand syntax:\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/command-ebnf.svg[command\nebnf]\n\nFor the purpose of this demonstration, `/buildbot` is simply enough.\n\nInternally a command comment will be converted into this structure:\n\n*Command structure (cmd/buildbot-app/command/command.go)*\n\n[source%linesnum,go]\n----\n// A Command represents all information about a /buildbot command\ntype Command struct {\n    // When true, the command has to pass for the PR in order pass gating\n    // (default: true).\n    IsMandatory bool\n    // Case-sensitive, sorted list of builders without duplicates to run build on.\n    // TODO(kwk): Maybe we can default to something reasonable here?\n    BuilderNames []string\n    // The user's GitHub login that issued the /buildbot comment\n    CommentAuthor string\n    // When true, we'll try to run the build even if the PR has already been\n    // tested at this stage (default: false).\n    Force bool\n}\n----\n\nThere’s a regular expression that a string comment must match (see\n`StringIsCommand()`) in order for it to be a valid command string:\n\n*Regular Rexpression (cmd/buildbot-app/command/command.go)*\n\n[source%linesnum,go]\n----\nconst (\n    // BuildbotCommand is the command that triggers the buildbot workflow in a\n    // GitHub comment.\n    BuildbotCommand = \"/buildbot\"\n\n    // CommandOptionMandatory is the boolean option to make a check run\n    // mandatory.\n    CommandOptionMandatory = \"mandatory\"\n\n    // CommandOptionBuilder is the option that can be used multiple times in a\n    // command comment. The resulting builders will be a case-sensitive, sorted\n    // list of builders with no duplicates.\n    CommandOptionBuilder = \"builder\"\n\n    // CommandOptionForce is the boolean option to enforce a new build even if\n    // one is already present.\n    CommandOptionForce = \"force\"\n)\n\n// StringIsCommand returns true if the given string is a valid /buildbot command.\nfunc StringIsCommand(s string) bool {\n    // force=yes|no : Can be used to allow for PRs to be build even when\n    // they are closed or when a check run for the exact same SHA has been\n    // run already.\n    return regexp.MustCompile(buildRegexPattern()).MatchString(s)\n}\n\n// buildRegexPattern returns the regex pattern to match a string against a\n// /buildbot command\nfunc buildRegexPattern() string {\n    tfOptions := `(yes|no|true|false|f|t|y|n|0|1)`\n    mandatoryOption := fmt.Sprintf(`%s=%s`, CommandOptionMandatory, tfOptions)\n    forceOption := fmt.Sprintf(`%s=%s`, CommandOptionForce, tfOptions)\n    builderOption := fmt.Sprintf(`%s=(\\w+)`, CommandOptionBuilder)\n    return fmt.Sprintf(`^%s(\\s+|%s|%s|%s)*$`, BuildbotCommand, mandatoryOption, forceOption, builderOption)\n}\n----\n\n[[_build_log_comment]]\n==== Build Log Comment\n\nThe `buildbot-app` then creates a *Thank-you*-comment that serves two\npurposes:\n\n[arabic]\n. It shows the user that we understood the request and are thankful for\nit and that we are working on it.\n. It is a perfect placeholder to store short build state changes for\nfuture lookups. That is why we call this comment the\n*build-log-comment*.\n+\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/screenshots/build-log-comment.png[build\nlog comment]\n+\nJust imagine, your PR gets updated and you want to see the previous\nbuild results. The _build-log-comment_ is there for you too look it up.\n\nThe code for creating the comment is straight-forward:\n\n*Thank You! (cmd/buildbot-app/on_issue_comment_event.go)*\n\n[source%linesnum,go]\n----\n     // This comment will be used all over the place\n        thankYouComment := fmt.Sprintf(\n            `Thank you @%s for using the \u003ca href=\"todo:link-to-documentation-here\"\u003e\u003ccode\u003e%s\u003c/code\u003e\u003c/a\u003e command \u003ca href=\"%s\"\u003ehere\u003c/a\u003e! `,\n            command.BuildbotCommand,\n            *event.Comment.User.Login,\n            *event.Comment.HTMLURL,\n        )\n\n        newComment, _, err := gh.Issues.CreateComment(context.Background(), repoOwner, repoName, prNumber, \u0026github.IssueComment{\n            Body: github.String(thankYouComment +\n                `\u003csub\u003eThis very comment will be used to continously log build state changes for your request. We decided to do this in addition to using Github's Check Runs below so you can inspect previous check runs better.\u003c/sub\u003e`,\n            ),\n        })\n----\n\n[[_check_run]]\n==== Check run\n\nOf course, we are also using GitHub’s check runs as you can see here:\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/screenshots/check-run-overview.png[check\nrun overview]\n\n[NOTE]\n====\nI really like that we can dynamically create check runs on request and\ngive them good names.\n====\n\nWhen you click on *Details* next to a check run, you’re brought to this\npage on GitHub:\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/screenshots/check-run-details.png[check\nrun details]\n\n[[_video_walkthrough]]\n==== Video walkthrough\n\nWe walk you through the creation of a Pull Request and authoring the\n`/buildbot` comment in this in this short video:\nhttps://www.youtube.com/watch?v=9NpbKEmkvt8\n\n[[_uml_sequence_diagram]]\n==== UML sequence diagram\n\nThe sequence diagram for this scenario is layed out here. It includes\nsome of the internals of the processing.\n\nimage:https://raw.githubusercontent.com/kwk/buildbot-app/main/docs/media/on-buildbot-comment.svg[on\nbuildbot comment]\n\n[[_testing]]\n=== Testing\n\n[[_testing_github_interaction]]\n==== Testing GitHub interaction\n\nWe’re using a fantastic library to run to simulate sequential GitHub\ninteraction: https://github.com/migueleliasweb/go-github-mock.\n\nFor example, when `/buildbot` comment is authored on a pull request we\ndon’t want a build to run if the pull request is not mergable. Therefore\nwe first have to take the event input and get the pull request from\nGitHub before we check if is mergable:\n\n*Get PR and check mergability\n(cmd/buildbot-app/on_issue_comment_event.go)*\n\n[source%linesnum,go]\n----\n     commentUser := *event.Comment.User.Login\n        repoOwner := *event.Repo.Owner.Login\n        repoName := *event.Repo.Name\n        prNumber := *event.Issue.Number\n        pr, _, err := gh.PullRequests.Get(context.Background(), repoOwner, repoName, prNumber)\n        if !pr.GetMergeable() {\n        }\n----\n\nIn order to test that a PR is not mergable, we can simply create a valid\n`github.PullRequest` object (see `prOK()`) and set the `Mergable` member\nto `false`. The mock server will return it as the first request and\nafterwards create a `POST` a comment about the pull request not being\nmergable:\n\n*Test: Get PR and check mergability\n(cmd/buildbot-app/on_issue_comment_event_test.go)*\n\n[source%linesnum,go]\n----\nfunc TestOnIssueCommentEventAny(t *testing.T) {\n    t.Run(\"pr not mergable\", func(t *testing.T) {\n        t.Run(\"comment writable\", func(t *testing.T) {\n            prNotMergable := prOK()\n            prNotMergable.Mergeable = github.Bool(false)\n            srv := NewMockServer(\n                // Get PR for comment event\n                mock.WithRequestMatch(\n                    mock.GetReposPullsByOwnerByRepoByPullNumber,\n                    prNotMergable,\n                ),\n                // Create comment on about PR not being mergable\n                mock.WithRequestMatch(\n                    mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber,\n                    github.IssueComment{\n                        Body: github.String(\"blabla\"),\n                    },\n                ),\n            )\n            fn := OnIssueCommentEventAny(srv)\n            err := fn(\"1234\", \"created\", issueCommentEventOK())\n            require.ErrorContains(t, err, \"pr is not mergable\", \"expected and error because pr is not mergable, yet\")\n        })\n    })\n}\n----\n\nFor this trick to work we have to use dependency injection by passing a\nGo interface (`Server`) instead of a real server object to functions in\nvarious places:\n\n*Server interface (cmd/buildbot-app/server.go)*\n\n[source%linesnum,go]\n----\n// Server specifies the interface that we need to implement from the AppServer\n// object in order to provide a decent mock in tests.\ntype Server interface {\n\n    // NewGithubClient returns a new GitHub client object for the given\n    // application ID.\n    NewGithubClient(appInstallationID int64) (*github.Client, error)\n\n    // RunTryBot runs a \"buildbot try\" command\n    RunTryBot(responsibleGithubLogin string, githubRepoOwner string, githubRepoName string, properties ...string) (string, error)\n}\n----\n\n[[_todos]]\n==== TODOs\n\n* ❏ Reset check run to neutral after Pull Request was updated.\n* ❏ Deal with buttons shown at the top of check run details page.\n\n[[_developer_setup]]\n== Developer Setup\n\nI’m using a Fedora Linux 37 on my local machine and for most of the\ncontainers.\n\n[source,console]\n----\n$ git clone https://github.com/kwk/buildbot-app.git \u0026\u0026 cd buildbot-app \n$ sudo dnf install -y direnv golang podman podman-compose buildbot pandoc asciidoctor \n$ gem install asciidoctor-lists pygments.rb \n$ go install github.com/cespare/reflex@latest \n$ cat \u003c\u003cEOF \u003e\u003e ~/.bashrc \nexport PATH=\\${PATH}:~/go/bin\neval \"\\$(direnv hook bash)\"\nEOF\n$ source ~/.bashrc \n$ direnv allow . \n$ make infra-start \n$ make app \n----\n\n* Clone the repo.\n* Install tools we need/use for development locally. If this was a\ndeployment site the only requirement is buildbot so that the github app\ncan make a call to `buildbot try`.\n* Install extension to create list of figures etc. and install pygments\nfor source code highlighting.\n* Install hot-reload tool.\n* Make tools above available upon next source of `.bashrc`.\n* Reload `.bashrc` to have `direnv` and `reflex` working in your current\nshell.\n* Navgigate out and back into the project directory to have `direnv`\nkickin. If this doesn’t work, try `direnv allow .`.\n* Bring up local containers for a buildbot setup with one master and\nthree workers.\n* Run and hot reload the app code upon changes being made to any of your\n`*.go` files or your `.envrc` file.\n\n[[_useful_links]]\n== Useful links\n\n[[_llvm_links]]\n== LLVM links\n\n* Discussion on LLVM Discourse:\nhttps://discourse.llvm.org/t/rfc-prototyping-pre-commit-testing-using-buildbot/69900?u=kwk\n\n[[_github_app_documents]]\n== Github App documents\n\n* Github Webhook Events and Payloads:\nhttps://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads\n* Github Apps documentation: https://docs.github.com/en/apps\n\n[[_interacting_with_github]]\n== Interacting with Github\n\n* Forwarding Github Webhooks to your local dev machine:\nhttps://dashboard.ngrok.com/get-started/setup\n* Github Emoji Cheat Sheet:\nhttps://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md\n\n[[_golang_libraries]]\n== Golang libraries\n\n* For using Github API v3 from Golang:\nhttps://github.com/google/go-github\n* GraphQL Go Library for Github API v4:\nhttps://github.com/shurcooL/githubv4\n* For mocking the above repo responses:\nhttps://github.com/migueleliasweb/go-github-mock\n* Go web framework: https://github.com/labstack/echo\n* For handling github events: https://github.com/cbrgm/githubevents\n* For authentication of Github App from private key file:\nhttps://github.com/bradleyfalzon/ghinstallation\n\n[[_buildbot_links]]\n== Buildbot links\n\n* System Architecture:\nhttps://docs.buildbot.net/latest/manual/introduction.html#system-architecture\n* Custom services (Might be worth looking into):\nhttps://docs.buildbot.net/latest/manual/configuration/services/index.html\n\n[[_misc_links]]\n== Misc links\n\n* Recording terminal sessions: https://github.com/faressoft/terminalizer\n* For automatic reloading: https://github.com/cespare/reflex\n* Per-Directory environment files: https://direnv.net/\n\n[[_lists]]\n== Lists\n\n[[_list_of_figures]]\n== List of figures\n\n[[_list_of_tables]]\n== List of tables\n\n[[_list_of_code_snippets]]\n== List of code snippets\n\nlink:#cd841b04-3fc5-4388-ba3c-1aca6d7f50c1[Listing 1.] Command structure\n(cmd/buildbot-app/command/command.go) +\nlink:#39ad47a6-8c85-47c8-bf25-f65e425de282[Listing 2.] Regular\nRexpression (cmd/buildbot-app/command/command.go) +\nlink:#5ac04254-7db6-46f2-b371-cb91339ace8e[Listing 3.] Thank You!\n(cmd/buildbot-app/on_issue_comment_event.go) +\nlink:#bf2bda60-e550-4332-b406-c32431dde101[Listing 4.] Get PR and check\nmergability (cmd/buildbot-app/on_issue_comment_event.go) +\nlink:#fae37627-0f58-4a6a-adb0-d03494b5c305[Listing 5.] Test: Get PR and\ncheck mergability (cmd/buildbot-app/on_issue_comment_event_test.go) +\nlink:#868c092a-3941-4971-b2f5-d570212c3c31[Listing 6.] Server interface\n(cmd/buildbot-app/server.go) +\n\n[[_todo]]\n== TODO\n\n* ❏ properly document developer setup with ngrok and how to setup the\n`.envrc` file\n* ❏ hook into buildbots event system and send feedback to buildbot app\nfrom there?\n\n[[_terminology]]\n== Terminology\n\nPR or Pull Request::\n  \"Pull requests let you tell others about changes you’ve pushed to a\n  branch in a repository on GitHub. Once a pull request is opened, you\n  can discuss and review the potential changes with collaborators and\n  add follow-up commits before your changes are merged into the base\n  branch.\"\n   — (https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests[About\n  pull requests])\nBuildmaster or Buildbot Master::\n  \"Buildbot consists of a single buildmaster and one or more workers\n  that connect to the master. The buildmaster makes all decisions about\n  what, when, and how to\n  build.\" — (https://docs.buildbot.net/latest/manual/introduction.html[Buildbot\n  System Architecture])\nBuildbot Worker::\n  \"The workers only connect to master and execute whatever commands they\n  are instructed to\n  execute.\" — (https://docs.buildbot.net/latest/manual/introduction.html[Buildbot\n  System Architecture])\nBuilder::\n  \"A builder is a user-configurable description of how to perform a\n  build. It defines what steps a new build will have, what workers it\n  may run on and a couple of other properties. A builder takes a build\n  request which specifies the intention to create a build for specific\n  versions of code and produces a build which is a concrete description\n  of a build including a list of steps to perform, the worker this needs\n  to be performed on and so\n  on.\" — (https://docs.buildbot.net/latest/manual/introduction.html[Buildbot\n  System Architecture])\nScheduler::\n  \"A scheduler is a user-configurable component that decides when to\n  start a build. The decision could be based on time, on new code being\n  committed or on similar\n  events.\" — (https://docs.buildbot.net/latest/manual/introduction.html[Buildbot\n  System Architecture])\nReporters::\n  Reporters are user-configurable components that send information about\n  started or completed builds to external sources. Buildbot provides its\n  own web application to observe this data, so reporters are optional.\n  However they can be used to provide up to date build status on\n  platforms such as GitHub or sending\n  emails. — (https://docs.buildbot.net/latest/manual/introduction.html[Introduction])\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwk%2Fbuildbot-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkwk%2Fbuildbot-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwk%2Fbuildbot-app/lists"}