GitLab Documentation

Adding a new Service to Omnibus GitLab

In order to add a new service to GitLab, you should follow these steps:

  1. Fetch and compile the software during build
  2. Add a top-level configuration object for the service
  3. Include the service in the services list
  4. Create enable and disable recipes for the service

Optionally another common task is to add additional configuration parsing for the service.

Fetch and compile the software during build

You need to add a Software Definition for your service if it is not already included in the project.

Software definitions, which can be found in /config/software, specify where omnibus should fetch the software, how to compile it and install it to the required folder. This part of the project is run when we build the Omnibus package for GitLab.

See other Software services in the directory for examples on how to include your software service.

Add a top-level configuration object for the service

The cookbooks and recipes located in files/gitlab-cookbooks are what get run during gitlab-ctl reconfigure in instances where the GitLab Omnibus package has been installed. This is where we will need to add the settings for a new service.

Define the default attributes

Pick one of the existing cookbooks to configure your service within, or create a new cookbook if your service warrants its own.

Within the cookbook there should be an attributes/default.rb file. This is where you want to define the Default Attributes for your service. For a service you should define an enable option by default.

default['gitlab']['best-service']['enable'] = false
default['gitlab']['best-service']['dir'] = '/var/opt/gitlab/best-service'
default['gitlab']['best-service']['log_directory'] = '/var/log/gitlab/best-service'

Define all your settings that you want configurable in the package here. Default them to nil if you need to calculate their defaults based on other settings for now.

Create a config Mash for your service

In order for user to be able to configure your service from /etc/gitlab/gitlab.rb you will need to add a top level Mash for the service.

In files/gitlab-cookbooks/gitlab/libraries/gitlab.rb you will find the list of Mash.new calls.

Add your service to the bottom, using an underscore to seperate words, even if you used a hyphen in the default attributes.

best_service Mash.new

You also need to add the service a little lower down in the generate_hash method. Add your service into the array. If you service exists within the attributes for the gitlab cookbook, you should add it in the array that gets appended to the gitlab results, the first array. Otherwise, if your service has its own cookbook, add it to the second array.

[
  "best_service"
].each do |key|
  rkey = key.tr('_', '-')
  results['gitlab'][rkey] = Gitlab[key]
end

Add service configuration to the settings template

We maintain a global config template where examples of how to configure the services are available, commented out.

This file becomes the /etc/gitlab/gitlab.rb on fresh installs of the package.

Once you want to expose your service's config to user for them to change, add it to this file. files/gitlab-config-template/gitlab.rb.template

### Best Service configuration
# best_service['enable'] = true
# best_service['dir'] = '/var/opt/gitlab/best-service'
# best_service['log_directory'] = '/var/log/gitlab/best-service'

Use the underscore syntax here for word separation. The values provided are not meant to reflect the defaults, but are to make it easier to uncomment to use the service. If that isn't possible you can use values clearly meant to be replaced like YOURSECRET etc. Or use the default when it makes the most sense.

Include the service in the services list

In order to allow the service to be easily enable/disabled within the recipes, it should be added to the services list and given appropriate groups.

In the files/gitlab-cookbooks/package/libraries/services.rb file, add the service to the appropriate Config class, Base or EE depending on whether the service is only for GitLab EE.

service 'best_service', groups: ['bestest']

We use the underscore word separation because these services act on the Mash objects we created earlier. Specifying groups makes it easier to disable/enable multiple related services as once.

If none of the existing groups match with what your service does, and you don't currently need to enable/disable the service using a group. Don't bother adding at this time.

Some examples of existing groups you may want to use:

Create enable and disable recipes for the service

Enable recipe

The enable recipe should be created as files/gitlab-cookbooks/<cookbook-name>/recipes/<service-name>.rb if it being added to an existing cookbook. If the service has its own cookbook, the enable recipe can be created as files/gitlab-cookbooks/<cookbook-name>/recipes/enable.rb.

In the recipe you will want to create the working directory in /var/opt/gitlab for your service. You will want to ensure the system user that runs your service is created. Render any configuration files needed for your service into your working directory.

Near the end of the recipe you will want to make a call to the Runit service definition to define your recipe. In order for this work work you will need to have created a run file in the cookbooks templates/default directory. These filenames start with sv- followed by the service name, followed by the runit action name.

A service typically needs a run, log-run, and log-config.

sv-best-service-log-config.erb:

<%= "s#@svlogd_size" if @svlogd_size %>
<%= "n#@svlogd_num" if @svlogd_num %>
<%= "t#@svlogd_timeout" if @svlogd_timeout %>
<%= "!#@svlogd_filter" if @svlogd_filter %>
<%= "u#@svlogd_udp" if @svlogd_udp %>
<%= "p#@svlogd_prefix" if @svlogd_prefix %>

sv-best-service-log-run.erb:

#!/bin/sh
exec svlogd -tt <%= @options[:log_directory] %>

sv-best-service-run.erb:

#!/bin/sh
exec 2>&1
<%= render("mount_point_check.erb") %>
cd <%= node['gitlab']['best-service']['dir'] %>
exec chpst -P /opt/gitlab/embedded/bin/best-service -config-flags -etc

Depending on what you are running, and which user should run it, your run file should be configured differently. Look in our other -run.erb for examples.

Within your recipe, the runit service should be called and started:

runit_service "best-service" do
  options({
    configItem: 'value'
  }.merge(params))
  log_options node['gitlab']['logging'].to_hash.merge(node['best-service'].to_hash)
end

if node['gitlab']['bootstrap']['enable']
  execute "/opt/gitlab/bin/gitlab-ctl start best-service" do
    retries 20
  end
end

Disable recipe

The enable recipe should be created as files/gitlab-cookbooks/<cookbook-name>/recipes/<service-name>_disable.rb if it being added to an existing cookbook. If the service has its own cookbook, the disable recipe can be created as files/gitlab-cookbooks/<cookbook-name>/recipes/disable.rb.

The recipe needs to container any cleanup you want to do when you service is disabled, and have a call to disable the runit service.

runit_service "best-service" do
  action :disable
end

Additional configuration parsing for the service

If you want to populate certain config options based on other options being set by the user, we add a library for your service to parse variables.

The library should be added as files/gitlab-cookbooks/<cookbook name>/libraries/<service-name>.rb

The library should be a module named after your service that has a parse_variables method.

module BestService
  class << self
    def parse_variables
      # setup some additional configuration based on the values of the user provided configuration
    end
  end
end

We then need to have the gitlab config call your parse_variables method.

Go into files/gitlab-cookbooks/libraries/gitlab.rb and all a require_relative for the library.

require_relative 'best-service.rb'

Then below, in the generate_config method. Add a call to your parse_variables, above the call to generate_hash.

BestService.parse_variables

Not that order in the generate_config method matters. So any config that the new service library expects to have been parsed should come first, and any other config parsing that depends on the new service's library should be parsed later in the list. Place the new service's parse method with that in mind.