diff --git a/_posts/2018-03-25-things-I-learnt-gitlab-pages-https-only.md b/_posts/2018-03-25-things-I-learnt-gitlab-pages-https-only.md new file mode 100644 index 0000000..088e61e --- /dev/null +++ b/_posts/2018-03-25-things-I-learnt-gitlab-pages-https-only.md @@ -0,0 +1,140 @@ +--- +title: Some things that I learned making GitLab Pages HTTPS-only +slug: things-I-learnt-gitlab-pages-https-only +layout: post +categories: 'development foss' +date: '2018-03-25 00:00:00 +0200' +--- + +This week, my most significant contribution yet to the [GitLab project](https://gitlab.com/) was [merged](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16273) into master. + +The contribution adds the ability for [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/index.html) websites to be served only over HTTPS connections, with plain HTTP requests 301-redirected to their secure counterpart. + +The changes required to make this work were not trivial, and actually required two separate patches to be merged: one in [Ruby](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16273) and a second in [Go](https://gitlab.com/gitlab-org/gitlab-pages/merge_requests/50). + +Here's a random sampling of some things that I learnt during the development process. + +### GitLab is a fun project to hack on + +The [GitLab application](http://gitlab.com/gitlab-org/gitlab-ce/) consists of a Rails monolith plus a number of secondary services, including a dedicated [HTTP server for GitLab Pages](https://gitlab.com/gitlab-org/gitlab-pages/) which is written in Go. + +GitLab Pages websites are typically auto-generated by the GitLab [CI/CD system](https://about.gitlab.com/features/gitlab-ci-cd/). The artifacts of this process are static website assets, such as HTML files, which are served by the Pages server. + +Implementing HTTPS-only pages required two separate changes to the overall application. + +Firstly, the Rails monolith had to be altered to write [additional metadata](https://gitlab.com/gitlab-org/gitlab-ce/blob/9d45951fcaeda4f01a2e4be2480d980a3e7cd37e/app/services/projects/update_pages_configuration_service.rb#L19-35) to a per-project configuration file, indicating whether the project's Pages website should be forced to HTTPS or not. Supporting this were the user interface changes to allow a user to enable or disable the behaviour, and the usual layers of automated testing. + +Secondly, the Go HTTP server had to be updated to parse the newly-added configuration, and adapt its behaviour accordingly (that is, serve a 301-redirect when appropriate). + +I love the challenge of implementing a distributed feature across multiple technical domains. Being forced to consider the interaction between the services, error-handling, reliability and so on means that it's possible to learn much more than the sum of the two individual pull requests. + +Additionally, GitLab's code quality in general feels very good. The Rails app is written in a modern style, with heavy use of [service objects](https://hackernoon.com/service-objects-in-ruby-on-rails-and-you-79ca8a1c946e) and comprehensive automated testing, including a very slick parallelized CI flow. + +Finally, the GitLab community appears to be very professional and friendly, and contributing a significant feature was a very positive experience. Thanks guys! + +### Go is a nice language + +Generally I distrust Google as a corporation, and this political bias had led me to be somewhat suspicious about Go as a language. This turns out to have been a fallacy, and it's been great to dispel it. + +Go is a much nicer language than I unfairly expected it to be. In its syntax, basic types and use of pointers it immediately reminded me of C, which made me feel comfortable very quickly. + +I also found the Go toolchain to be very productive and enjoyable (although given Google's investment in the ultra-slick Android toolchain this probably shouldn't be a surprise). Being able to import modules directly from Git repositories, and auto-format code from the command-line are just two simple examples. + +This project helped me to understand why Go has become so popular, and when it might be considered the right tool to choose in the future. + +### Parameterized RSpec tests are a great thing + +When writing RSpec tests, it is common to start with this kind of pattern: + +{% highlight ruby %} +RSpec.describe "something" do + before do + send_request(path, "GET") + end + + context "path is /" do + let(:path) { "/" } + + it { is_successful } + end + + context "path is /abc" do + let(:path) { "/abc" } + + it { is_successful } + end +end +{% endhighlight %} + +This feels simple and reasonably readable, until a second contextual variable is introduced. Not only do we quickly see code-repetition being introduced, but the nesting also starts to feel unwieldy: + +{% highlight ruby %} +RSpec.describe "something" do + before do + send_request(path, method) + end + + context "path is /" do + let(:path) { "/" } + + context "method is GET" do + let(:method) { "GET" } + it { is_successful } + end + + context "method is POST" do + let(:method) { "POST" } + it { is_not_successful } + end + end + + context "path is /abc" do + let(:path) { "/abc" } + + context "method is GET" do + let(:method) { "GET" } + it { is_successful } + end + + context "method is POST" do + let(:method) { "POST" } + it { is_not_successful } + end + end +end +{% endhighlight %} + +Introducing even more contextual levels exacerbates the problem further. + +Enter [RSpec::Parameterzied](https://github.com/tomykaira/rspec-parameterized), which allows the same examples to be defined using a simple, flat table instead: + +{% highlight ruby %} +RSpec.describe "something" do + using RSpec::Parameterized::TableSyntax + + where(:path, :method, :success) do + "/" | "GET" | true + "/abc" | "GET" | true + "/" | "POST" | false + "/abc" | "POST" | false + end + + with_them do + before do + send_request(path, "GET") + end + + it "returns the expected response" do + expect(response.success).to eq success + end + end +end +{% endhighlight %} + +RSpec will now run each of the rows in the table as a separate example, with inputs corresponding to the column values. + +Although a downside of this approach is that debugging the specs can be more challenging, in test-heavy applications with a lot of contextual variants this feels like a useful option. + +### Conclusions + +Implementing HTTPS-only GitLab Pages has been a great learning experience, and a lot of fun! I'd highly recommend anybody who wants to hack on a non-trivial, backend-centric Rails app to check out the [GitLab Contributing page](https://about.gitlab.com/contributing/) and get involved.