-
Notifications
You must be signed in to change notification settings - Fork 3
Add New Language
This document covers the necessary steps to take to add support for a new language to BoxCI. We will cover this in sections with respect to each of BoxCI commands.
In this document we will go through the process of adding support for the Ruby language. This is broken out into the following sections:
- Requirements for
boxci init
- Requirements for
boxci build
- Requirements for
boxci test
- Overview of Result
In order for boxci init
to work with a particular language we need to get the language to be registered as a supported language. This is a two step process:
- Create Language Class Skeleton
- Require the Language Skeleton
- Verify Language is registered
The first step in the process is to define the skeleton of the Boxci::Languages::Ruby
class. This should exist in a file located at lib/boxci/languages/ruby.rb
and would have the following content as a starting skeleton. Note: If you were adding support for Java it would exist in lib/boxci/languages/java.rb
and would have a class name of Java
.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
raise "Needs to be implemented"
end
def generate_starter_puppet_manifest
raise "Needs to be implemented"
end
end
end
end
end
Next you need to edit lib/boxci/boxci.rb
and add the following line so that BoxCI will properly load your new language class. This should be added below the require 'boxci/language'
require 'boxci/languages/ruby'
You can verify that the Language is properly registered by running the following command:
bundle exec bin/boxci help init
If all is good you should now see your language appear in the Supported Languages:
call out toward the end of the help message. This would look something like the following:
Usage:
boxci init [-p PROVIDER] LANGUAGE
Options:
-p, [--provider=PROVIDER]
# Default: virtualbox
Description:
`boxci init [-p PROVIDER] LANGUAGE` will create a .boxci directory in your user's home directory, create a provider
specific config (ex: ~/.boxci/providers/virtualbox.yml), set the default provider in (~/.boxci/global_config.yml),
and create a project config (.boxci.yml) in the current working directory.
LANGUAGE is required, it is the language of your project, see supported languages below.
--provider (-p) is optional. If omitted, it will use your configured default provider (virtualbox). If you don't have
a default provider configured it will default to 'virtualbox'.
Supported Languages: ruby
Supported Providers: virtualbox, aws, openstack
In order for boxci build
to work with a particular language we need two things.
- Implement the
generate_starter_puppet_manifest
method in the language class - Verify
boxci build
properly generates the starter puppet manifest
Implementing this in the lib/boxci/languages/ruby.rb
file would look as follows.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
raise "Needs to be implemented"
end
def generate_starter_puppet_manifest
@project_config = Boxci.project_config
if @project_config.rbenv
inside Boxci.project_path do
run "git submodule add -f git@github.com:alup/puppet-rbenv.git puppet/modules/rbenv"
run "git submodule update --init"
end
end
template "templates/languages/#{Boxci.project_config.language}/main.pp", File.join(Boxci.project_path, "puppet/manifests/main.pp")
end
end
end
end
end
Based on the implementation of the generate_starter_puppet_manifest
method defined above. You can see that it also depends on a language specific template existing at lib/boxci/templates/languages/ruby/main.pp
. This template should be whatever you want the initial puppet manifest to be. In the case of ruby we want it to be the following.
exec { "apt-update":
command => "/usr/bin/apt-get update"
}
Exec["apt-update"] -> Package <| |>
package {
"rbenv":
ensure => present,
require => Exec['apt-update'],
}
rbenv::install { $vagrant_user:
home => $vagrant_home,
require => [Package['rbenv']],
}
<% if @project_config.rbenv %>
<% @project_config.rbenv.each do |ruby_version| -%>
rbenv::compile { "$vagrant_user/<%= ruby_version -%>":
user => $vagrant_user,
home => $vagrant_home,
ruby => '<%= ruby_version -%>',
require => Rbenv::Install["$vagrant_user"],
}
<% end -%>
<% end %>
You can verify boxci build
properly generates the starter puppet manifest by first boxci init
a project with your new language and then running boxci build
and seeing if it produced the starter puppet manifest the way you wanted. Note: It is also important that you test the resulting puppet manifest and make sure that it works as you want.
In order to properly support the boxci test
command and all its functionality a number of things need to happen.
- Implement required
default_script
method - Optionally implement
before_permutation_switch
method - Optionally implement
after_permutation_switch
method - Verify
boxci test
works properly
At this point to support boxci test
functionality you need to implement the default_script
method in the language class. This method is responsible for returning a string representation of what the default script
hook command should be. Note: For more info on the script
hook please refer to BoxCI - Build Lifecycle.
Implementing this in the lib/boxci/languages/ruby.rb
file would look as follows.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
%q{bundle exec rake}
end
def generate_starter_puppet_manifest
@project_config = Boxci.project_config
if @project_config.rbenv
inside Boxci.project_path do
run "git submodule add -f git@github.com:alup/puppet-rbenv.git puppet/modules/rbenv"
run "git submodule update --init"
end
end
template "templates/languages/#{Boxci.project_config.language}/main.pp", File.join(Boxci.project_path, "puppet/manifests/main.pp")
end
end
end
end
end
Optionally a language can also define commands that should happen before a permutation switch in the test runner. This would be the case for example if you had projects that specify specific of versions of the languages, BoxCI - Choose runtime versions.
Implementing this in the lib/boxci/languages/ruby.rb
file would look as follows.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
%q{bundle exec rake}
end
def before_permutation_switch
%q{export RAILS_ENV=test}
end
def generate_starter_puppet_manifest
@project_config = Boxci.project_config
if @project_config.rbenv
inside Boxci.project_path do
run "git submodule add -f git@github.com:alup/puppet-rbenv.git puppet/modules/rbenv"
run "git submodule update --init"
end
end
template "templates/languages/#{Boxci.project_config.language}/main.pp", File.join(Boxci.project_path, "puppet/manifests/main.pp")
end
end
end
end
end
In the above example in the before_permutation_switch
I have set the RAILS_ENV
environment variable to a value of test
. I did this because in the case of Rails apps we specifically want to make sure that we are in the test
environment. In the case of non-Rails ruby projects having RAILS_ENV
set does not hurt anything.
Optionally a language can also define commands that should happen after a permutation switch in the test runner. This would be the case for example if you had projects that specify specific versions of the languages, BoxCI - Choose runtime versions.
Implementing this in the lib/boxci/languages/ruby.rb
file would look as follows.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
%q{bundle exec rake}
end
def before_permutation_switch
%q{export RAILS_ENV=test}
end
def after_permutation_switch
%q{gem install bundler && bundle install --without production}
end
def generate_starter_puppet_manifest
@project_config = Boxci.project_config
if @project_config.rbenv
inside Boxci.project_path do
run "git submodule add -f git@github.com:alup/puppet-rbenv.git puppet/modules/rbenv"
run "git submodule update --init"
end
end
template "templates/languages/#{Boxci.project_config.language}/main.pp", File.join(Boxci.project_path, "puppet/manifests/main.pp")
end
end
end
end
end
In the above example in the after_permutation_switch
I have it making sure that Bundler is installed and install the projects dependencies using Bundler. I did this because I am making an opinionated decision here that anyone developing Ruby or Ruby on Rails apps that is using BoxCI will be using Bundler.
At this point you should have a fully functioning language integrated into BoxCI and you should be able to test it through the boxci init
, boxci build
and now with the boxci test
command to make sure everything is working.
If you have made it this far then you should have a properly functioning language added to BoxCI ready to make pull request to get it added to be peer reviewed and added to the mainline repository. To recap the actual files we created and their end resulting content/changes we have the following.
module Boxci
module Languages
class Ruby < ::Boxci::Language
no_commands do
def default_script
%q{bundle exec rake}
end
def before_permutation_switch
%q{export RAILS_ENV=test}
end
def after_permutation_switch
%q{gem install bundler && bundle install --without production}
end
def generate_starter_puppet_manifest
@project_config = Boxci.project_config
if @project_config.rbenv
inside Boxci.project_path do
run "git submodule add -f git@github.com:alup/puppet-rbenv.git puppet/modules/rbenv"
run "git submodule update --init"
end
end
template "templates/languages/#{Boxci.project_config.language}/main.pp", File.join(Boxci.project_path, "puppet/manifests/main.pp")
end
end
end
end
end
require 'boxci/languages/ruby'
exec { "apt-update":
command => "/usr/bin/apt-get update"
}
Exec["apt-update"] -> Package <| |>
package {
"rbenv":
ensure => present,
require => Exec['apt-update'],
}
rbenv::install { $vagrant_user:
home => $vagrant_home,
require => [Package['rbenv']],
}
<% if @project_config.rbenv %>
<% @project_config.rbenv.each do |ruby_version| -%>
rbenv::compile { "$vagrant_user/<%= ruby_version -%>":
user => $vagrant_user,
home => $vagrant_home,
ruby => '<%= ruby_version -%>',
require => Rbenv::Install["$vagrant_user"],
}
<% end -%>
<% end %>
Thats it. Not much code at the end of the day.