<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eli Bendersky's website - Version control</title><link href="https://eli.thegreenplace.net/" rel="alternate"></link><link href="https://eli.thegreenplace.net/feeds/version-control.atom.xml" rel="self"></link><id>https://eli.thegreenplace.net/</id><updated>2024-05-04T19:46:23-07:00</updated><entry><title>GitHub Actions: first impressions</title><link href="https://eli.thegreenplace.net/2020/github-actions-first-impressions/" rel="alternate"></link><published>2020-09-25T20:13:00-07:00</published><updated>2022-10-04T14:08:24-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2020-09-25:/2020/github-actions-first-impressions/</id><summary type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="https://travis-ci.com/"&gt;Travis CI&lt;/a&gt; fairly extensively since
2013, when I moved my personal OSS projects &lt;a class="reference external" href="https://eli.thegreenplace.net/2013/06/09/switching-my-open-source-projects-from-bitbucket-to-github"&gt;from Bitbucket to GitHub&lt;/a&gt;.
It's a great service and a much-appreciated boon to the open-source community.&lt;/p&gt;
&lt;p&gt;However, since Travis &lt;a class="reference external" href="https://blog.travis-ci.com/2018-05-02-open-source-projects-on-travis-ci-com-with-github-apps"&gt;announced that their .org variant is shutting down soon&lt;/a&gt;,
I wanted to check out some …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using &lt;a class="reference external" href="https://travis-ci.com/"&gt;Travis CI&lt;/a&gt; fairly extensively since
2013, when I moved my personal OSS projects &lt;a class="reference external" href="https://eli.thegreenplace.net/2013/06/09/switching-my-open-source-projects-from-bitbucket-to-github"&gt;from Bitbucket to GitHub&lt;/a&gt;.
It's a great service and a much-appreciated boon to the open-source community.&lt;/p&gt;
&lt;p&gt;However, since Travis &lt;a class="reference external" href="https://blog.travis-ci.com/2018-05-02-open-source-projects-on-travis-ci-com-with-github-apps"&gt;announced that their .org variant is shutting down soon&lt;/a&gt;,
I wanted to check out some of the alternatives, and GitHub actions (GHA) seemed
very interesting.&lt;/p&gt;
&lt;p&gt;So this week I've migrated &lt;a class="reference external" href="https://github.com/eliben/pycparser"&gt;pycparser&lt;/a&gt;
and a few of my other OSS projects over to GHA. This turned out to be very easy!
Here's a brief recap.&lt;/p&gt;
&lt;img alt="GitHub actions icon" class="align-center" src="https://eli.thegreenplace.net/images/2020/gha-icon.png" /&gt;
&lt;div class="section" id="workflow-configuration"&gt;
&lt;h2&gt;Workflow configuration&lt;/h2&gt;
&lt;p&gt;To activate GHA for pycparser, all I had to do is create the following YAML
file as &lt;tt class="docutils literal"&gt;.github/workflows/ci.yml&lt;/tt&gt; in the repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;name: pycparser-tests
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  build:

    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        python-version: [2.7, 3.6, 3.7, 3.8]
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:

    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - name: Test
      run: |
        python tests/all_tests.py
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;This workflow fires on two kinds of events: pushes to the master branch and
PRs to the master branch. Each PR will have an automatic CI run for each
change (every new commit pushed).&lt;/li&gt;
&lt;li&gt;It runs in multiple configurations: the cross-product of Python versions and
OSes, as specified.&lt;/li&gt;
&lt;li&gt;The &lt;tt class="docutils literal"&gt;run:&lt;/tt&gt; entry is the command the runs the tests.&lt;/li&gt;
&lt;li&gt;While &lt;tt class="docutils literal"&gt;pycparser&lt;/tt&gt; doesn't have any dependencies, it's easy to have those too
by adding &lt;tt class="docutils literal"&gt;pip install $whatever&lt;/tt&gt; lines to &lt;tt class="docutils literal"&gt;run:&lt;/tt&gt; before the actual test
execution line.&lt;/li&gt;
&lt;/ul&gt;
&lt;object class="align-center" data="https://eli.thegreenplace.net/images/2020/gha-badge.svg" type="image/svg+xml"&gt;GitHub tests passed badge&lt;/object&gt;
&lt;/div&gt;
&lt;div class="section" id="first-impressions"&gt;
&lt;h2&gt;First impressions&lt;/h2&gt;
&lt;p&gt;My first impressions of GHA compared to Travis:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Actions run &lt;em&gt;much faster&lt;/em&gt;; the CI jobs schedule pretty much immediately. On
Travis you might have to wait for multiple minutes.&lt;/li&gt;
&lt;li&gt;Out-of-the-box Windows and Mac OS option! I couldn't get these with the free
Travis variant and had to augment my CI solution for pycparser by running on
Windows through &lt;a class="reference external" href="https://www.appveyor.com/"&gt;AppVeyor&lt;/a&gt;. Now I only need
to maintain a single CI workflow.&lt;/li&gt;
&lt;li&gt;Travis seems to have better documentation and configurability at this point;
while the GHA documentation is comprehensive, it's a bit scattered and harder
to follow. This is something I hope will improve over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I like what I'm seeing from GHA so far; the ability to set up a CI workflow
very easily without bouncing between multiple Web UIs is a blessing, and GHA
appears to be a capable, performant platform with a convenient selection of
OSes.&lt;/p&gt;
&lt;p&gt;I'm still using Travis for some projects and will continue comparing the two
over the coming months.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Software &amp; Tools"></category><category term="Version control"></category><category term="Python"></category></entry><entry><title>How to send good pull requests on GitHub</title><link href="https://eli.thegreenplace.net/2019/how-to-send-good-pull-requests-on-github/" rel="alternate"></link><published>2019-11-06T06:15:00-08:00</published><updated>2022-10-04T14:08:24-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2019-11-06:/2019/how-to-send-good-pull-requests-on-github/</id><summary type="html">&lt;p&gt;Over the past few years I authored or reviewed thousands of GitHub pull
requests (PRs), both for work and for personal projects. I've come to believe
there's a small set of useful rules of thumb for what makes a good PR, what
makes a bad PR, and why getting the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over the past few years I authored or reviewed thousands of GitHub pull
requests (PRs), both for work and for personal projects. I've come to believe
there's a small set of useful rules of thumb for what makes a good PR, what
makes a bad PR, and why getting the good ones merged is much easier - both for
the PR author and the reviewer.&lt;/p&gt;
&lt;p&gt;Here's a quick checklist for a good PR. Each item is described in more detail
below.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Make sure the PR is needed&lt;/li&gt;
&lt;li&gt;Have an open issue linked in (optional)&lt;/li&gt;
&lt;li&gt;Write a useful PR title&lt;/li&gt;
&lt;li&gt;Write a detailed PR description&lt;/li&gt;
&lt;li&gt;Adhere to the project's coding standards&lt;/li&gt;
&lt;li&gt;Add tests&lt;/li&gt;
&lt;li&gt;Make sure all tests pass&lt;/li&gt;
&lt;li&gt;Be patient and friendly during code review&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="section" id="make-sure-the-pr-is-needed"&gt;
&lt;h2&gt;Make sure the PR is needed&lt;/h2&gt;
&lt;p&gt;This is especially important if you're contributing to a repository you haven't
worked with much before. Do some research in the existing issues and PRs in the
repository - including closed ones. Is this change already being discussed
somewhere? Was it proposed before and rejected? The code you want to change - is
it there for a good reason?&lt;/p&gt;
&lt;p&gt;GitHub offers reasonably good search capabilities in case the project has a
large log of issues in PRs. It's not perfect, but by running a few searches with
probable keywords there's a good chance to find something. Another thing I often
do is search the commit history of a project for relevant information (&lt;tt class="docutils literal"&gt;git log
&lt;span class="pre"&gt;--grep&lt;/span&gt;&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Demonstrating some due diligence goes a long way in showing the repository owner
that you're a serious contributor.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="have-an-open-issue-linked-in-optional"&gt;
&lt;h2&gt;Have an open issue linked in (optional)&lt;/h2&gt;
&lt;p&gt;An important tool of modern software development discipline is having an
open &lt;em&gt;issue&lt;/em&gt; (or &lt;em&gt;bug&lt;/em&gt;, or &lt;em&gt;ticket&lt;/em&gt;, or however else it's called in other
systems) to discuss some problem or some missing feature we
want to address.&lt;/p&gt;
&lt;p&gt;An issue is more general than a PR description. An issue describes a problem; a
PR describes a solution to that problem. Some issues require multiple PRs to be
solved, and interlinking all these PRs through the issue is critical for later
attempts at archaeology.&lt;/p&gt;
&lt;p&gt;If in doubt - open an issue. Add all the context there. The PR will then
reference the issue with a &lt;tt class="docutils literal"&gt;#&amp;lt;issue number&amp;gt;&lt;/tt&gt; tag - this is something GitHub
understands and will add a link between the two. A PR can also say &lt;tt class="docutils literal"&gt;Fixes
#&amp;lt;issue number&amp;gt;&lt;/tt&gt; if merging this PR means the issue is fully solved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One of the most frustrating experiences for a repository maintainer is getting
a PR without sufficient context of what it attempts to solve and why&lt;/strong&gt;. Having
an open issue with all the details is the best way to establish this context;
the next sections address some additional ways.&lt;/p&gt;
&lt;p&gt;I marked this section as &lt;em&gt;(optional)&lt;/em&gt; because an issue isn't necessary in some
cases. For example, typos in comments typically don't require an issue and a PR
carries sufficient context. Minor changes in documentation also don't require
issues in most cases.&lt;/p&gt;
&lt;p&gt;If in doubt, create an issue. Linking this to the previous section - if an issue
describing the problem already exists, make sure to link your PR to it -
maintainers &lt;em&gt;love&lt;/em&gt; PRs that solve open issues.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="write-a-useful-pr-title"&gt;
&lt;h2&gt;Write a useful PR title&lt;/h2&gt;
&lt;p&gt;This advice will read a bit like &amp;quot;what makes a good git commit message&amp;quot;.&lt;/p&gt;
&lt;p&gt;The PR title is extremely important. It's what people see when listing all open
PRs. It's also commonly translated to be the first line of the merged commit,
and shows up prominently in &lt;tt class="docutils literal"&gt;git log&lt;/tt&gt;, etc. Take special care in crafting the
PR title to be descriptive and useful, but not too long.&lt;/p&gt;
&lt;p&gt;Some large repositories have special guidelines for writing PRs. For example,
the PR title would start with the component name - &lt;em&gt;&amp;quot;storage/remote:
increase widget timeout&amp;quot;&lt;/em&gt;. Look around - how do other PRs (that were
successfully merged) look? Is there any contribution guide in the repository
that details these conventions?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="write-a-detailed-pr-description"&gt;
&lt;h2&gt;Write a detailed PR description&lt;/h2&gt;
&lt;p&gt;This ties strongly to the &amp;quot;Have an open issue linked in&amp;quot; advice. If the PR
requires a long background description, it's better to do this in an issue and
have a link in the PR. If there is no issue for some reason, the burden is on
the PR description to explain the motivation for the change, and the approach
taken in it.&lt;/p&gt;
&lt;p&gt;But PRs and issues are also for diferent purposes. Sometimes, a PR description
will have information that doesn't belong in an issue, such as details of the
specific approach taken in the PR, benchmark numbers for this PR, etc.&lt;/p&gt;
&lt;p&gt;The PR description will make it into the git commit log - add as much detail as
you can. The repository mainainer can later tweak the commit log so they will
remove things they don't need; if in doubt, add more details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adhere-to-the-project-s-coding-standards"&gt;
&lt;h2&gt;Adhere to the project's coding standards&lt;/h2&gt;
&lt;p&gt;Does the project have a contributors guide? Spend a couple of minutes looking
for it.&lt;/p&gt;
&lt;p&gt;Look at other PRs that were merged - what did their authors do?&lt;/p&gt;
&lt;p&gt;Look at some of the existing code in the repository - try to match the style of
your PR to the prevailing style in the existing code. Doing this shows the
maintainer that you're a serious contributor who cares about the long term
health of the project.&lt;/p&gt;
&lt;p&gt;Be attentive to the smallest details: how much whitespace does the code have,
including in comments? Is there a specific writing style - Oxford commas, one or
two spaces after a period, and so on?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="add-tests"&gt;
&lt;h2&gt;Add tests&lt;/h2&gt;
&lt;p&gt;If you're adding new code - make sure it's tested. Either add new tests, or
point out in the PR description which existing tests cover it. If a test is
too hard to add for some reason, explain why.&lt;/p&gt;
&lt;p&gt;For changing existing code the situation is a bit more nuanced. Is the change
addressing a current test failure? Which one? Which tests are affected by the
change? Should new tests be added?&lt;/p&gt;
&lt;p&gt;Spend a few minutes thinking about this and documenting your conclusions in the
PR description. Again, this shows the maintainer that you're a serious
contributor and your PR is more likely to get attention.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="make-sure-all-tests-pass"&gt;
&lt;h2&gt;Make sure all tests pass&lt;/h2&gt;
&lt;p&gt;Many GitHub repositories have integration with CI tools, whereupon each PR gets
automatically tested and the CI system adds notes to the PR about its passing
or failing.&lt;/p&gt;
&lt;p&gt;After sending a PR, watch out for this and address any failures. Maintainers are
unlikely to pay attention to PRs that break the build or tests.&lt;/p&gt;
&lt;p&gt;If the repository has no such tool, make sure to run all the tests you find in
the project and ensure that your change doesn't affect them negatively. If some
test breaks because it's bad, make sure to fix it. If some tests fail with or
without your change, make sure to call this out in the PR description.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="be-patient-and-friendly-during-code-review"&gt;
&lt;h2&gt;Be patient and friendly during code review&lt;/h2&gt;
&lt;p&gt;Once the PR is finally out, the contributor's task isn't done yet. In fact, most
of the work may yet be ahead. The code review process is an important tenet of
modern software development, and knowing how to behave is critical for success.&lt;/p&gt;
&lt;p&gt;Whole book chapters have been written on code reviews, so I'll keep it short and
simple here, focusing on open-source contributions. When sending a PR for a work
project, it's obvious we should be extra friendly and kind with colleagues,
right? &lt;em&gt;Right&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;But what about open source? You found an issue in some OSS project you use, and
are sending a PR. Good for you! Do you expect the maintainer will be delighted
about the contribution? Well, not necessarily, and it really depends on your
demeanor.&lt;/p&gt;
&lt;p&gt;OSS maintainers are notoriously overworked and underpaid. Sometimes they just
want stability - as few changes as possible. Clearly if you report a critical
bug they will likely be happy to fix it; but 99% of PRs are not for critical
bugs - they are for minor bugs and new features. Here the PR presents a
dilemma for the maintainer - someone is sending code, and this someone is very
likely going to completely disappear after the PR lands, so the responsibility
for the code moves to the maintainer. It's not surprising that in many cases,
maintainers are cautions and even suspicious of every change.&lt;/p&gt;
&lt;p&gt;When answering review comments be patient and friendly. Assume good
intentions - the maintainer is taking extra burden to maintain additional code
(especially with feature PRs) and it's their right to scrutinize it and take
their time. Multiple rounds of reviews may be required. Be sure to be responsive
and attentive to detail - acknowledge all comments, either by doing what the
reviewer suggests, or (kindly) explaining your point of view. Add more tests if
they ask you to (and you can't point to existing tests covering the same thing),
add more comments if they ask you to.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Software &amp; Tools"></category><category term="Version control"></category></entry><entry><title>GitHub webhook payload as a cloud function</title><link href="https://eli.thegreenplace.net/2019/github-webhook-payload-as-a-cloud-function/" rel="alternate"></link><published>2019-03-05T06:20:00-08:00</published><updated>2024-05-04T19:46:23-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2019-03-05:/2019/github-webhook-payload-as-a-cloud-function/</id><summary type="html">&lt;p&gt;Back in 2014, I wrote &lt;a class="reference external" href="https://eli.thegreenplace.net/2014/07/09/payload-server-in-python-3-for-github-webhooks"&gt;a post describing a simple payload server&lt;/a&gt;
for GitHub webhooks, using Python 3. That server could be deployed to any VPS
listening on a custom port.&lt;/p&gt;
&lt;p&gt;Now it's 2019, and deploying servers to VPSs doesn't make me feel hip enough.
All the cool kids are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Back in 2014, I wrote &lt;a class="reference external" href="https://eli.thegreenplace.net/2014/07/09/payload-server-in-python-3-for-github-webhooks"&gt;a post describing a simple payload server&lt;/a&gt;
for GitHub webhooks, using Python 3. That server could be deployed to any VPS
listening on a custom port.&lt;/p&gt;
&lt;p&gt;Now it's 2019, and deploying servers to VPSs doesn't make me feel hip enough.
All the cool kids are into &lt;em&gt;serverless&lt;/em&gt; now, so I decided to rewrite the same
payload server in Go and deploy it as a Google Cloud Function. This brief post
can serve as a basic tutorial on how to do it.&lt;/p&gt;
&lt;p&gt;I assume you already have a GCP account (there's a free tier), and the
&lt;tt class="docutils literal"&gt;gcloud&lt;/tt&gt; command-line tool is configured to authenticate with your account and
project name.&lt;/p&gt;
&lt;p&gt;You can see the &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2019/github-hook-google-cloud-function"&gt;full code here&lt;/a&gt;,
but this is the important part:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ioutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Header:\n---------&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validateSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Signature validation failed&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Body:\n---------&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;Payload&lt;/tt&gt; is a standard &lt;tt class="docutils literal"&gt;http.HandlerFunc&lt;/tt&gt;, and its signature should be
familiar to anyone who has written HTTP servers in Go. GitHub sends its payload
as a POST request (hence our reading from &lt;tt class="docutils literal"&gt;r.Body&lt;/tt&gt;) with some special headers
for validation. The validation code runs a SHA1 HMAC to ensure that GitHub
knows a secret key shared with the application (this helps keep intruders away
from your payload server).&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://github.com/eliben/code-for-blog/tree/main/2019/github-hook-google-cloud-function"&gt;full code sample&lt;/a&gt;
has this validation code, as well as a simple unit test for &lt;tt class="docutils literal"&gt;Payload&lt;/tt&gt;. It
doesn't actually attempt to create a properly signed message, but checks that
&lt;tt class="docutils literal"&gt;Payload&lt;/tt&gt; is alive and returns a valid HTTP response. In general, it is highly
recommended to unit-test these handlers locally, because cloud function
deployment takes many seconds and isn't very convenient for short edit-test
cycles.&lt;/p&gt;
&lt;p&gt;To deploy this function, we'll go to the directory where &lt;tt class="docutils literal"&gt;payloadserver.go&lt;/tt&gt;
lives, and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ gcloud functions deploy payloadserver \
      --entry-point Payload \
      --runtime go111 \
      --trigger-http \
      --set-env-vars HOOK_SECRET_KEY=&amp;lt;your secret key&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This prints out a URL for your function; it looks something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;httpsTrigger:
  url: https://&amp;lt;region&amp;gt;-&amp;lt;project-name&amp;gt;.cloudfunctions.net/payloadserver
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which is what you'll point the GitHub webhook to. Configure the webhook to send
all events, and then test it by creating or modifying some issue in your
repository. The webhook management page on GitHub should now show the event
in &amp;quot;Recent deliveries&amp;quot;. You can also check the logs of your cloud function,
either from the GCP control panel, or from the command-line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ gcloud functions logs read payloadserver
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If everything ran successfully, you'll see the headers and the body of the
payload emitted to the log. That's it!&lt;/p&gt;
&lt;p&gt;Jokes about hipness aside, once you get the hang of it cloud functions seem like
a particularly easy way to deploy simple web servers and apps for specific
needs. There's a lot of powerful functionality behind the simple facade - for
example, resources will automatically scale with the load. That said, be aware
of the costs if you're doing it for anything high-volume.&lt;/p&gt;
</content><category term="misc"></category><category term="Go"></category><category term="Version control"></category><category term="Network Programming"></category></entry><entry><title>Payload server in Python 3 for Github webhooks</title><link href="https://eli.thegreenplace.net/2014/07/09/payload-server-in-python-3-for-github-webhooks" rel="alternate"></link><published>2014-07-09T05:50:49-07:00</published><updated>2022-10-04T14:08:24-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2014-07-09:/2014/07/09/payload-server-in-python-3-for-github-webhooks</id><summary type="html">
        &lt;p&gt;The Github &lt;a class="reference external" href="https://developer.github.com/webhooks/"&gt;Webhooks API&lt;/a&gt; is powerful and flexible, making it simple to integrate services with your source repository. Lately I've been tinkering with it a bit, but all the examples Github has are in Ruby. So I put together a simple demo server in Python 3. Though simple (it's completely …&lt;/p&gt;</summary><content type="html">
        &lt;p&gt;The Github &lt;a class="reference external" href="https://developer.github.com/webhooks/"&gt;Webhooks API&lt;/a&gt; is powerful and flexible, making it simple to integrate services with your source repository. Lately I've been tinkering with it a bit, but all the examples Github has are in Ruby. So I put together a simple demo server in Python 3. Though simple (it's completely self contained and only needs Python 3 to run), it's complete, covering even &lt;a class="reference external" href="https://developer.github.com/webhooks/securing/"&gt;webhook security&lt;/a&gt; by verifying the signature created with the API's secret token.&lt;/p&gt;
&lt;p&gt;Here it is:&lt;/p&gt;
&lt;div class="highlight" style="background: #ffffff"&gt;&lt;pre style="line-height: 125%"&gt;&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;argparse&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;hashlib&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;hmac&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;from&lt;/span&gt; &lt;span style="color: #00007f"&gt;http.server&lt;/span&gt; &lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; HTTPServer, BaseHTTPRequestHandler
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;json&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;pprint&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;os&lt;/span&gt;
&lt;span style="color: #00007f; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #00007f"&gt;sys&lt;/span&gt;

&lt;span style="color: #007f00"&gt;# It&amp;#39;s not recommended to store the key within the code. Following&lt;/span&gt;
&lt;span style="color: #007f00"&gt;# http://12factor.net/config, we&amp;#39;ll store this in the environment.&lt;/span&gt;
&lt;span style="color: #007f00"&gt;# Note that the key must be a bytes object.&lt;/span&gt;
HOOK_SECRET_KEY = os.environb[b&lt;span style="color: #7f007f"&gt;&amp;#39;HOOK_SECRET_KEY&amp;#39;&lt;/span&gt;]


&lt;span style="color: #00007f; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #00007f"&gt;GithubHookHandler&lt;/span&gt;(BaseHTTPRequestHandler):
    &lt;span style="color: #7f007f"&gt;&amp;quot;&amp;quot;&amp;quot;Base class for webhook handlers.&lt;/span&gt;

&lt;span style="color: #7f007f"&gt;    Subclass it and implement &amp;#39;handle_payload&amp;#39;.&lt;/span&gt;
&lt;span style="color: #7f007f"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span style="color: #00007f; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00007f"&gt;_validate_signature&lt;/span&gt;(&lt;span style="color: #00007f"&gt;self&lt;/span&gt;, data):
        sha_name, signature = &lt;span style="color: #00007f"&gt;self&lt;/span&gt;.headers[&lt;span style="color: #7f007f"&gt;&amp;#39;X-Hub-Signature&amp;#39;&lt;/span&gt;].split(&lt;span style="color: #7f007f"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;)
        &lt;span style="color: #00007f; font-weight: bold"&gt;if&lt;/span&gt; sha_name != &lt;span style="color: #7f007f"&gt;&amp;#39;sha1&amp;#39;&lt;/span&gt;:
            &lt;span style="color: #00007f; font-weight: bold"&gt;return&lt;/span&gt; &lt;span style="color: #00007f"&gt;False&lt;/span&gt;

        &lt;span style="color: #007f00"&gt;# HMAC requires its key to be bytes, but data is strings.&lt;/span&gt;
        mac = hmac.new(HOOK_SECRET_KEY, msg=data, digestmod=hashlib.sha1)
        &lt;span style="color: #00007f; font-weight: bold"&gt;return&lt;/span&gt; hmac.compare_digest(mac.hexdigest(), signature)

    &lt;span style="color: #00007f; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00007f"&gt;do_POST&lt;/span&gt;(&lt;span style="color: #00007f"&gt;self&lt;/span&gt;):
        data_length = &lt;span style="color: #00007f"&gt;int&lt;/span&gt;(&lt;span style="color: #00007f"&gt;self&lt;/span&gt;.headers[&lt;span style="color: #7f007f"&gt;&amp;#39;Content-Length&amp;#39;&lt;/span&gt;])
        post_data = &lt;span style="color: #00007f"&gt;self&lt;/span&gt;.rfile.read(data_length)

        &lt;span style="color: #00007f; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #0000aa"&gt;not&lt;/span&gt; &lt;span style="color: #00007f"&gt;self&lt;/span&gt;._validate_signature(post_data):
            &lt;span style="color: #00007f"&gt;self&lt;/span&gt;.send_response(&lt;span style="color: #007f7f"&gt;401&lt;/span&gt;)
            &lt;span style="color: #00007f; font-weight: bold"&gt;return&lt;/span&gt;

        payload = json.loads(post_data.decode(&lt;span style="color: #7f007f"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))
        &lt;span style="color: #00007f"&gt;self&lt;/span&gt;.handle_payload(payload)
        &lt;span style="color: #00007f"&gt;self&lt;/span&gt;.send_response(&lt;span style="color: #007f7f"&gt;200&lt;/span&gt;)


&lt;span style="color: #00007f; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #00007f"&gt;MyHandler&lt;/span&gt;(GithubHookHandler):
    &lt;span style="color: #00007f; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #00007f"&gt;handle_payload&lt;/span&gt;(&lt;span style="color: #00007f"&gt;self&lt;/span&gt;, json_payload):
        &lt;span style="color: #7f007f"&gt;&amp;quot;&amp;quot;&amp;quot;Simple handler that pretty-prints the payload.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style="color: #00007f; font-weight: bold"&gt;print&lt;/span&gt;(&lt;span style="color: #7f007f"&gt;&amp;#39;JSON payload&amp;#39;&lt;/span&gt;)
        pprint.pprint(json_payload)


&lt;span style="color: #00007f; font-weight: bold"&gt;if&lt;/span&gt; __name__ == &lt;span style="color: #7f007f"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
    argparser = argparse.ArgumentParser(description=&lt;span style="color: #7f007f"&gt;&amp;#39;Github hook handler&amp;#39;&lt;/span&gt;)
    argparser.add_argument(&lt;span style="color: #7f007f"&gt;&amp;#39;port&amp;#39;&lt;/span&gt;, &lt;span style="color: #00007f"&gt;type&lt;/span&gt;=&lt;span style="color: #00007f"&gt;int&lt;/span&gt;, help=&lt;span style="color: #7f007f"&gt;&amp;#39;TCP port to listen on&amp;#39;&lt;/span&gt;)
    args = argparser.parse_args()

    server = HTTPServer((&lt;span style="color: #7f007f"&gt;&amp;#39;&amp;#39;&lt;/span&gt;, args.port), MyHandler)
    server.serve_forever()
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just run it at some port on your server and point the webhook you create to it. Currently it just runs on the server's root path (e.g. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;http://myserver.com:1234&lt;/span&gt;&lt;/tt&gt;), but should be trivial to modify to any path.&lt;/p&gt;
&lt;p&gt;By the way, I found &lt;a class="reference external" href="https://ngrok.com/"&gt;ngrok&lt;/a&gt; to be invaluable for testing this. It creates a tunnel from your localhost's port to a unique URL you can set as the webhook destination on Github. This makes it possible to quickly iterate and test the server on your local machine.&lt;/p&gt;

    </content><category term="misc"></category><category term="Python"></category><category term="Version control"></category><category term="Network Programming"></category></entry><entry><title>Moving to Github - one year recap</title><link href="https://eli.thegreenplace.net/2014/06/08/moving-to-github-one-year-recap" rel="alternate"></link><published>2014-06-08T08:50:47-07:00</published><updated>2023-02-04T13:41:52-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2014-06-08:/2014/06/08/moving-to-github-one-year-recap</id><summary type="html">
        &lt;p&gt;It's been a year since I &lt;a class="reference external" href="https://eli.thegreenplace.net/2013/06/09/switching-my-open-source-projects-from-bitbucket-to-github/"&gt;switched all my personal projects&lt;/a&gt; from Bitbucket / Mercurial to Github / Git, so I think the time is right for a brief recap.&lt;/p&gt;
&lt;img class="align-center" src="https://eli.thegreenplace.net/images/2014/06/pythocat-e1401975025529.png" /&gt;
&lt;p&gt;As I mentioned back then, the platform (Github vs. Bitbucket) served a larger role in the decision to switch than the source …&lt;/p&gt;</summary><content type="html">
        &lt;p&gt;It's been a year since I &lt;a class="reference external" href="https://eli.thegreenplace.net/2013/06/09/switching-my-open-source-projects-from-bitbucket-to-github/"&gt;switched all my personal projects&lt;/a&gt; from Bitbucket / Mercurial to Github / Git, so I think the time is right for a brief recap.&lt;/p&gt;
&lt;img class="align-center" src="https://eli.thegreenplace.net/images/2014/06/pythocat-e1401975025529.png" /&gt;
&lt;p&gt;As I mentioned back then, the platform (Github vs. Bitbucket) served a larger role in the decision to switch than the source control system (Git vs. Mercurial). Bitbucket lets you use Git now if you want, so the latter difference is actually moot. It's the platform that remains the difference, and if I felt that Github has &amp;quot;won&amp;quot; the platform war a year ago, I feel even more so now. Even so far as being called the new résumé for developers, Github has definitely captured a very prominent place in the jargon and habits of our craft.&lt;/p&gt;
&lt;p&gt;Amazingly, Bitbucket still hasn't caught up in terms of front-page information. On Github it's fairly easy, in a quick glance, to assess how active a developer is, which projects he contributes to, etc. On Bitbucket, all you see is a dull list of project names, not sorted in any useful way. Organizations, another cool Github concept, is also nowhere to be seen. Perhaps Bitbucket gave up on this fight, though, focusing on corporate users.&lt;/p&gt;
&lt;p&gt;My main qualm with Bitbucket was that due to its lack of popularity I didn't get as much contribution to my projects as I hoped for. Had this improved? Certainly! Instead of &amp;quot;why don't you use Github&amp;quot; complaints, I now routinely get pull requests (or at least opened issues) for my projects. Several of &lt;a class="reference external" href="https://github.com/eliben/"&gt;my projects&lt;/a&gt; are starred by dozens of developers, and the most popular one - &lt;a class="reference external" href="https://github.com/eliben/pycparser"&gt;pycparser&lt;/a&gt; - now has over 40 forks!&lt;/p&gt;
&lt;p&gt;Github pull requests are indeed a thing of beauty. Not that there's anything technically complex about them. It's a cultural thing. If you find a problem in a project, just send in a pull request. Most chances are it will be accepted. Pull requests have become a ubiquitous contribution currency, because of one very subtle, yet powerful factor. Github pull requests are part of your online identity. Once your pull request is merged into a project, it's forever retained in the log that you have contributed to that project. &lt;em&gt;You&lt;/em&gt; being the direct link to your Github account / profile. For example, I commited a small fix to Django a few months ago - &lt;a class="reference external" href="https://github.com/django/django/commit/73f51e411372ba3e74ccf5a2c2be88927ac2c6dd"&gt;here it is&lt;/a&gt;, forever recorded and linked to my Github profile. The power of Github as the de-facto social network of developers cannot be ignored. It is way, &lt;em&gt;way&lt;/em&gt; more significant than Linkedin.&lt;/p&gt;
&lt;p&gt;Let's put it this way - &lt;em&gt;actual commits beat empty endorsements, any day&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Github indeed serves the résumé role well, at least for me. I routinely receive job / interview offers or recruiter pitches based on my &amp;quot;impressive Github profile&amp;quot;.&lt;/p&gt;
&lt;p&gt;Even projects that are not formally hosted on Github take care to maintain a mirror there, because Github is such a familiar low-entry way to examine their code and even fork it. It's not uncommon for people to develop their changes in branches vs. the Github mirror, and sending patches to the real source repository when done. I know this happens in the LLVM &amp;amp; Clang world a lot. Maintaining Github mirrors of projects is quite easy - for example, it took me just a few hours to set up &lt;a class="reference external" href="https://github.com/python/cpython"&gt;this semi-official Github mirror for CPython&lt;/a&gt;, and as for maintaining it... well, there's cron for that.&lt;/p&gt;
&lt;p&gt;Github's popularity is also the cause of it being the first target of other projects that aim to make the life of developers easier. &lt;a class="reference external" href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt; is a blessing, and I use its Github integration extensively. All developers know how painful it is to set up and maintain continuous integration builds for their project. Travis &amp;amp; Github make this ridiculously easy, at least for the limited set of platforms Travis currently supports. Automatically run tests for each proposed pull request and tell me if it breaks anything? Shut up and take my money! Oh, it's free for open-source projects...&lt;/p&gt;

    </content><category term="misc"></category><category term="Programming"></category><category term="Version control"></category></entry></feed>