A DSL lib for your SingularityCI instance.
Inspired by TravisCI's .travis.yml
...Except with the full power of Ruby, its gems & your awesome JenkinsCI machine(s).
All you need is a .singularityrc
file in your repository, the singularity_runner
(provided by this gem) and you're ready to go!
$ gem install singularity_dsl
Commands:
singularity_runner batch BATCH_NAME # Run single task batch in the .singularityrc script.
singularity_runner help [COMMAND] # Describe available commands or one specific command
singularity_runner tasks # Available tasks.
singularity_runner test # Run singularity script.
singularity_runner testmerge FORK BRANCH INTO_BRANCH [INTO_FORK] # Perform a testmerge into the local repo and then run .singularityrc
Options:
-t, [--task-path=TASK_PATH] # Directory where custom tasks are defined
# Default: ./.singularity
-a, [--all-tasks], [--no-all-tasks] # Do not stop on task failure(s), collect all results
-s, [--script=SCRIPT] # Specify path to a .singularityrc file
# Default: ./.singularityrc
[--env=one two three] # EnvVars to set, formatted as VAR:VAL
[--flags=one two three] # Runtime flags to set for use with flag_set?, formatted as VAR:VAL
The singularity_runner
is designed to do two things:
- load custom tasks from the repository you're running it in (
.singularity
dir by default). - read a
.singularityrc
file.
Just a ruby file describing what you want to run. The commands that are run are called task
s. For example:
shelltask { command 'bundle' }
rubocop
rspec
Running singularity_runner test
in the same directory as this file, everything just gets executed. Simple, right?
What's actually happening is:
- the runner first loads up any task definitions. Built in task definitions here
- runner then looks for any custom defined definitions in your
cwd/.singularity
dir - loads the entire
.singularityrc
file into an internally managed DSL object - depending on what command is run, evaluates blocks in
.singularityrc
You can even define batch
es of tasks to be run. So for example:
batch :test do
rubocop
rspec
end
shelltask { command 'bundle' }
Running singularity_runner test
on this file will only run the bundle
shelltask. Why? Because defining a batch
does not run anything. It's just a way for your to organize what tasks should be run together.
To actually run it, you need this line: invoke_batch :test
Or, if you just want to run that one batch, without the bundle
shelltask, you can tell the runner to do just that! singularity_runner batch test
A task is just a ruby class. For base functionality, it needs an execute
method. You can have it do whatever you want in that method.
To further customize it, you can define a description
method that returns a string with some info about the task.
Tasks also take ruby blocks.
What this means is that you can pass blocks of code to tasks from your .singularityrc
. Those blocks of code would then be executed in the context of the Task. Think resources in chef. You can use these blocks to configure how certain task declarations run.
As mentioned, singularity_runner
can load custom tasks or task extensions. By default, it will load all .rb
files in cwd/.singularity
This allows you to do a few things:
- abstract out common tasks that you use to build, test, etc your code
- configure tools / tasks with default values specific to your use case
- create a common lib of task definitions to be shared amoungst your repositories
To see a list of base tasks, their class names, what their names are in .singularityrc
's context, you just have to run:
singularity_runner tasks
Which yields something like:
Task Task Function Description
Rake rake Simple resource to just wrap the Rake CLI
RSpec rspec Run RSpec tests. Uses RSpec::Core
Rubocop rubocop Runs rubocop, loads .rubocop.yml from ./
ShellTask shelltask Runs a SH command using Mixlib::ShellOut
Note that there is a task called shelltask
, defined by a ruby Task class called ShellTask
.
Say you wanted to create a task for a common echo command. You can simply create a ruby file in cwd/.singularity
, say echo.rb
class Echo extends ShellTask
def execute
command 'echo "hello"'
super
end
end
The singularity_runner tasks
command should then list your task as one of the tasks usable from your cwd.
Task Task Function Description
Rake rake Simple resource to just wrap the Rake CLI
RSpec rspec Run RSpec tests. Uses RSpec::Core
Rubocop rubocop Runs rubocop, loads .rubocop.yml from ./
ShellTask shelltask Runs a SH command using Mixlib::ShellOut
Echo echo Runs the Echo task.
The Echo
task does a couple of things. Take a look at the ShellTask class.
All Echo
is doing is setting the shell command in the parent class to echo "hello"
& then calling it. Nothing special here, but you can hopefully see that this opens up a lot of possibilities.
Task
Base task that all tasks extend from. If an instance of this class is ever used, or if a child class does not define execute
, an error is raised.
Configuration Methods | Description |
---|---|
N/A | N/A |
Rake
Run a rake task!
Configuration Methods | Description |
---|---|
target |
What Rake task to execute |
RSpec
Run a suite of rspec tests.
Configuration Methods | Description |
---|---|
config_file |
Where rspec config file is |
spec_dir |
Where rspec tests are |
Rubocop
Run rubocop.
Configuration Methods | Description |
---|---|
config_file |
Where Rubocop.yml is |
file |
Add a file to the list of files to run rubocop against |
ShellTask
Run a SH task using Mixlib::ShellOut
Configuration Methods | Description |
---|---|
no_fail |
Runner does not fail if this task fails to run |
command |
Shell command to execute |
alt |
Alternative shell command to execute (used in conjunction with condition ) |
condition |
Shell command to execute. If successful, run the command set via command , otherwise run alt |
These are blocks of tasks that you can run depending on the status of the runner (i.e.: whether all of your tasks succeeded in running, failed, errored, etc).
Block Name | Description |
---|---|
on_success |
invoked when singularity_runner runs everything (via test , testmerge , batch ) successfully |
on_error |
invoked when singularity_runner errors out while trying to run something in your .singularityrc , after it has been processed |
on_fail |
invoked when any of your tasks error out (e.g.: shelltask returns a non-zero exit code) |
always |
always invoked after a .singularityrc run |
singularity_runner testmerge
has a very bare-bones implementation of a git merge. Given a fork, a branch in that fork, a base_repo & base_branch, the runner will:
- merge fork:branch into base_repo:base_branch
- perform a diff between the two
- take the file list (the changeset) and inject it into a running instance of the DSL
- run the
test
command (unless the-r
flag is given with a batch name, then it runs a batch)
Why would you do this? To conditionally execute blocks of tasks or batches based on what files changed! This can help in automating test workflows in your CI system, testing merges into trunk, etc.
But how?
The DSL exposes 2 methods to help you determine whether you want to execute blocks of code or not:
Method | Args | Return | Desc |
---|---|---|---|
files_changed? |
String or Array |
Boolean |
Performs a file extension regex match, returns true if any files in the changeset match, false otherwise |
changed_files |
String or Array |
Array |
Returns all files that have extensions that match the given values, returns an array of those files |
So for example, say you only want to execute rspec tests when there are Ruby file changes. You can do something like this:
batch :ruby do
shelltask { command 'bundle' }
rake { target 'build_app' }
rspec
end
batch :test_merge do
if files_changed? 'js'
shelltask { command 'grunt testjs' }
end
if files_changed? 'rb'
rubocop { files changed_files('rb') }
invoke_batch :ruby
end
end
Running
singularity_runner testmerge [email protected]:me/repo feature-branch master [email protected]:org/repo -r test_merge
Will perform the test merge & then pass ALL of the changed files in that merge into the .singularityrc
!
The singularity_runner
allows you to set custom environment variables for your scripts & tools to use.
singularity_runner test --env MY_REPO:[email protected]:me/foo
Will set ENV['MY_REPO'] = '[email protected]:me/foo'
Similarly, you can also set flags to better direct more complicated workflows.
singularity_runner test --flags npm phpunit-suite:ci
Will allow you to run something like this:
# works! npm == true here
if flag? 'npm' do
npm { action 'install' }
end
# suite_name == 'ci' here
suite_name = flag? 'phpunit-suite' || 'default'
phpunit { suite suite_name }
Assuming that you have already defined npm
& phpunit
tasks.
The DSL also keeps a record of where each task is defined. To get that path:
SingularityDsl.task_file(self)
This is just a utility function provided to you when writing new Tasks.
You can use it in your .singularityrc
but it's not recommended that you do.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
Make sure you run the tests!
bundle exec rake test:all