You may think that CI (Continuous Integration) consists only of technical tasks like unit testing, code compilation or static code analysis. However, sometimes you may want to put a little bit of fun to boring build logs :) If you are familiar with Jenkins you may have heard of Chuck Norris plugin which among others adds funny facts/jokes about Chuck Norris to the builds.
Before I will show you how to add such functionality to Bitrise.io, let me explain, what Bitrise.io is.
Bitrise is a Continuous Integration and Delivery (CI/CD) Platform as a Service (PaaS) with the main focus on mobile app development. At Droids On Roids, we switched our CI/CD platform from Jenkins to Bitrise a few years ago. The main reason of that relocation was a better adaptation to mobile (native Android and iOS projects) and faster bug fixes (e.g. this my PR in Jenkins Android emulator plugin has been waiting about 1.5 years for review). However, one small additional feature was missing on Bitrise… there were no integrations for Chuck Norris jokes.
The process of adding a joke to a build log is pretty simple. We just need to obtain a joke text and print it to the log. Of course, we can’t forget about proper error handling.
Let’s start with installing Bitrise CLI and invoking bitrise :step create
. You can find more info about step creation in my previous blogpost: How to Create Bitrise Step in Go – Flutter Example.
Bitrise officially supports two toolkits for writing integrations (so-called steps): bash (shell scripts) and go. At a glance, this integration looks trivial so it may be a good candidate for a shell script. However, since we need some basic logic like validations or error handling and Bitrise provides libraries which can help us, we will use golang toolkit.
Jenkins plugin uses hardcoded joke texts. It is the simplest source to implement. However, it also has a major drawback. Adding a new joke would require releasing a new version of the Bitrise step each time. Each of those versions would need to be approved and users would need to update step version in their workflows (unless they selected “always latest” version). Usually, it is a good idea to separate data from the logic.
We can get jokes from external, free-of-charge API. For example chucknorris.io. This one contains many different jokes, it is open source and supports plain text responses so it fits perfectly our needs. However, there are also other alternatives.
Although task performed by this step may look straightforward — we just need to download jokes and display them, it may be also useful to introduce a little bit of flexibility by allowing some parameters to be configurable. First of them is API host URL, one may want to use a different one than public chucknorris.io e.g. self-hosted instance due to privacy reasons. All the backend components of chucknorris.io are open source so it shouldn’t be a problem.
Secondly, we may want to customize jokes category. One may prefer some particular one. There is a query parameter in API for that.
Configuration parameters are specified using step inputs. They appear as form fields in workflow editor GUI and are injected as environment variables to the build. Newly generated step contains sample input. We can base on it when adding actual inputs. After customization, inputs section should look like this:
Note several things:
category
and api_base_url
) are written using lower snakecase.api_base_url
is marked as required, this fact will be reflected in the workflow editor.api_base_url
has a default value set to https://api.chucknorris.io
so users don't need to specify it explicitly.OK, we have inputs defined. Now we can try to read their values. Fortunately, Bitrise provides steputils library which does all the work for us. We only need to define data structure for config:
Note different naming convention in Go than in YAML. Name mapping, as well as additional properties (whether the value is required or not), are specified using struct tags. Now we can simply read inputs from the environment:
If there is an error (e.g. required input is not present) we print a message to a build log and exit with failure code, next instructions won’t be executed.
The process of downloading jokes can be split into several stages. First, we need to create an HTTP request based on configuration parameters (API host URL and category):
Note that we request plain text response so there is no need to process it in the next steps to obtain actual joke text. All the errors are propagated to the caller, we will deal with them in the top-level function.
Next, we perform the request:
The only customization here is a timeout set to 20 seconds. It is important since by default timeout is infinite so step may hang the build.
Finally, we can read text from the response:
We need to explicitly check the HTTP status code. Without that, in case of errors, we could joke texts like 500 Internal server error. Note that the error message starts with lowercase. It’s an error to capitalize them since they will be later appended to a prefix.
After connecting all these steps together we should get code like this:
Apart from invoking aforementioned functions we also have deferred response body closing. It will be closed after enclosing functions finishes. Note that if closing fails we cannot return this error to the caller. We log it instead of just ignoring. Swallowing errors is considered a bad practice and static code analysis tools like errcheck will complain about that.
When we have a joke text the only thing we need to do is printing it to the build log, which just contains everything printed to the standard output and standard error during build. Additionally, we can export joke text as a step output so it may be potentially used by next steps e.g. by posting inside a slack message. Finally, our main (top-level) function looks like this:
Note that we used log
from go-utils to print a colored message. In case of success we don't need to explicitly exit with code 0
since it's the default value. Moreover exporting needs to be done using envman which adds it to the envstore accessible by next steps. If we just set the environment variable for the current process it will be lost after step finishes. Keep in mind that output should be declared in step.yml
in order to be visible in workflow editor. By convention output names are written using upper snakecase:
By default, when step on Bitrise fails then the whole build is considered failed, and next steps are not executed. Those behaviors do not match our use case. We want to get the joke always (even if some of the previous steps has failed) and we don’t want those failures during getting jokes to affect the overall build result. E.g. user rather won’t be happy that pull request status check has failed only due to the error during downloading Chuck Norris joke and PR cannot be merged.
To achieve desired behaviors we need to adjust step configuration by setting **is_always_run**
and **is_skippable**
to true. The first one tells that step should be started even build is marked as failed (some of the previous steps has failed). The user will be able to override that setting in the workflow editor. Skippable step means that its failures won't affect overall build status – it can be still successful. If one really needs a joke, it is possible to override that setting in bitrise.yml
configuration file.
We’re 100% office based team with 7-years’ experience in mobile & web app development
One of the elements in Bitrise steplib submission checklist says that step should have test workflow inside its repo. By default, it consists of running the step. However, Bitrise provides few utilities useful in Continuous Integration of steps written in Go: golint, errcheck and Go test. All those steps need to know which is the Go package under test. The easiest way to provide that is to use Go list step. The complete workflow looks like this:
Note that we added go vet there. It’s an official Golang static analysis tool.
Bitrise devcenter contains tutorial about sharing own steps. However, there is also share-this-step
workflow which combines all those instructions. Step sharing process requires MY_STEPLIB_REPO_FORK_GIT_URL
environment variable. It can be provided using .bitrise.secrets.yml
file as follows:
By default, this file is ignored and not checked into version control. We’ve also added one little improvement. Instead of hardcoding step version in BITRISE_STEP_VERSION
variable we retrieve the current git tag: --tag $(git describe --tags --exact-match)
. In this case there is no need to assign the same version in 2 different places - in git tag and in bitrise.yml
. There is only one source of truth.
As you can see, Continuous Integration consists not only of technical tasks, you may also do funny things :) In case of Bitrise CI platform we can easily develop and share our own extensions in form of steps.
The full source code is available on GitHub.