Gem Version Code Climate Code Climate Build Status Dependency Status

Jisota

Jisota is a simple provisioning tool meant for smaller projects.

Provisioning, in this context, is the act of turning an empty server into a working machine tailored to your needs.

Note: Jisota is still considered unstable. Breaking changes may occur in patch version updates.

Installation

Add this line to your application's Gemfile:

gem 'jisota', require: false

And then execute:

$ bundle

Or install it yourself as:

$ gem install jisota

Introduction

A simple example of a Jisota script:

# config/provision.rb (Or whatever you want to call this file)
require 'jisota'

config = Jisota.config do
  role :app do
    ruby version: "2.1.1"
    nginx config_file: "path/to/nginx.conf"
  end

  server "mydomain.com", user: "john_doe", roles: :app
end

Jisota.run(config)

Run the script with the ruby executable:

$ ruby config/provision.rb

Ruby version

Jisota is cutting edge and requires Ruby 2.1.0 or later.

Defining a package

Inside a Jisota.config block, use the package method to define a package.

Example:

Jisota.config do
  package :essentials do
    description "Installs important stuff"
    run do
      apt :curl, :vim, :git
    end
  end
end

In this simple example we define a package with the name :essentials. It has a description and runs another package, :apt, that it calls with the arguments :curl, :vim, :git

The DSL you can use inside a package include:

  • description: Will provide a description for the package.
  • param: Specify one or more named parameters for the package. See more in the params section below.
  • run(&block): This block will be called when the package is executed.
  • verify(&block): If this block exits with code 0, the run block will not be executed

Inside a run-block, packages can be called by their name, somewhat like a Ruby method. See the apt example above.

Having multiple run and verify blocks

Sometimes you need to verify only parts of the package. Just specify multiple run and verify blocks:

package :install_awesome_service do
  description "Installs a service and starts it"

  run { cmd "sudo apt-get install awesome-service" }
  verify { cmd "command-to-tell-if-service-is-installed" }

  run { cmd "sudo service awesome start" }
  verify { cmd "command-to-tell-if-service-is-started" }
end

You can optionally name each of these sections to give a more precise output:

package :install_awesome_service do
  description "Installs a service and starts it"

  section "Install the service from apt-get"
  run { cmd "sudo apt-get install awesome-service" }
  verify { cmd "command-to-tell-if-service-is-installed" }

  section "Start the service"
  run { cmd "sudo service awesome start" }
  verify { cmd "command-to-tell-if-service-is-started" }
end

Defining packages to be used in more than one project

Defining packages with Jisota.config as above will only add the package to the current configuration. To make packages global they need to be in the global package manager. Simply use the Jisota.global_config instead:

Jisota.global_config do
  package :ruby do
    description "Installs ruby from source"
    ...
  end
end

This will make the package accessable from all configurations. If a local package has the same name, the local package is used.

All build-in packages are defined this way.

Defining params for a package

Params for a package are defined with the param DSL method inside a package-block. Params are named:

package :my_package do
  param :foo
  param :bar
  ...
end

This package can be called either with named parameters or with sorted parameters. The following calls are all equivalent:

my_package 42, "Baz"
my_package 42, bar: "Baz"
my_package foo: 42, bar: "Baz"
my_package bar: "Baz", foo: 42

Param options

Params can have the following options:

  • default: Sets a default value, if no value is passed to that parameter
  • required: Validates that the parameter will have a value
  • splat: Will assign all the following unnamed arguments to this parameter as an array

Example:

package :apt do
  param :packages, splat: true
  param :command, default: "sudo apt-get install :package"
  run do
    cmd command.gsub(/:package/, packages.join(' '))
  end
end

# And then call params inside another script block:
run do
  apt :vim, :git
  apt :vim, :git, command: "sudo apt-get install -y :package"
end

Defining a server

A server needs at least a host, a user and a role:

server "mydomain.com", user: "john", roles: :app

Other options:

  • key: File path to ssh private key

Atomic script operations

All packages will eventually boil down to these few atomic operations, which can be called inside a script block:

cmd

Will run a script on the server. Example:

cmd "apt-get install foo"

upload

Uploads the file to the server. Example:

upload from: "path/to/nginx.conf", to: "/etc/nginx.conf"

You can register script blocks to be called if the file was created or updated. If the file on the server is identical with the uploaded file, no action is done and the file will not be overwritten.

upload from: "foo", to: "bar" do
  create { cmd 'echo "File was created!"' }
  update { cmd 'echo "File was updated!"' }
end

You can disable creation or update of the file:

upload from: "foo", to: "bar" do
  create false # Only update is allowed. File will not be created if it does not exists
end

- OR -

upload from: "foo", to: "bar", create: false

You can also use package params and call packages inside these blocks:

package :nginx_config do
  param :config_file, require: true
  run do
    upload from: config_file, to: "/etc/nginx.conf" do
      update do
        cmd %q{echo "#{config_file} was updated. Restarting nginx..."}
        nginx_restart
      end
      create do
        cmd %q{echo "#{config_file} was started. Starting nginx..."}
        nginx_start
      end
    end
  end
end

Complete list of build-in packages

ruby

description "Installs ruby from source"
param :version, required: true
param :tmp_dir, default: "~/tmp"

apt

description "Installs packages with apt-get"
param :packages, required: true, splat: true

gem_install

description "Installs a gem"
param :gem_name, required: true
param :sudo, default: true

nginx_passenger

description "Install nginx with passenger module"
param :config_file, required: true

nginx

description "Install nginx from apt"
param :config_file

More packages?

Jisota is a young gem. Please contribute with any packages that you think others could benifit from.

To DSL or not to DSL

The DSL provided by Jisota is just a layer of abstraction. The entire library can easily be used without the DSL. This could be useful if you need to e.g. dynamically build a configuration.

Example:

my_package = Jisota::Package.new(:stuff)
my_package.params << Jisota::Param.new(:foo, default: 42)

config = Jisota::Configuration.new
config.packages << my_package

is equivalent to:

config = Jisota.config do
  package :stuff do
    param :foo, default: 42
  end
end

Full example

An example using most of the features of Jisota.

Note: This was not tested and probably won't make sense in a real application. This is just meant to show some features. A good place to look for examples are in the build-in packages

require 'jisota'

config = Jisota.config do
  package :essentials do
    description "Install essentials"
    param :extra_packages, splat: true
    run do
      packages = %w[git vim curl]
      packages += extra_packages
      cmd "sudo apt-get install #{packages.join(" ")}"
    end
  end

  package :postgres do
    description "Installs postgres"
    run do
      cmd "some command sequence to install postgres"
    end
  end

  package :nginx do
    description "Install nginx"
    param :config_file, required: true
    run do
      cmd "sudo apt get install nginx"
      upload from: config_file, to: /etc/nginx.conf do
        update { cmd "sudo service nginx restart" }
        create { cmd "sudo service nginx start" }
      end
    end
  end

  role :app do
    essentials "libfoo"
    ruby version: "2.1.1"
    nginx
  end

  role :db do
    essentials
    postgres
  end

  server "myapp.com",         user: "deploy", roles: :app
  server "staging.myapp.com", user: "deploy", roles: :app
  server "db.myapp.com",      user: "deploy", roles: :db
  server "standby.myapp.com", user: "deploy", roles: [:app, :db]
end

Jisota.run(config)

Testing

Unit tests

Run with rake

Acceptance tests

Acceptance tests are not meant to be run regularly. Some of them might take a long time to finish. They are a way to run Jisota and the build-in packages in a near-real environment that is easy to destroy and rebuild.

Start the vagrant:

$ rake vagrant:up

Then run acceptance specs

$ rake spec:acceptance

Or run individual specs. Some of them might take a while:

$ ACCEPTANCE=1 rspec spec/acceptance/some_spec.rb

To kill vagrant and start with a fresh machine:

$ rake vagrant:rebuild

Contributing

Any pull requests, suggestions, bug reports and feedback are most welcome :)

  1. Fork it ( http://github.com/lasseebert/jisota/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request