141 lines
6.3 KiB
Markdown
141 lines
6.3 KiB
Markdown
|
---
|
||
|
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.
|